Flutter 入门简介
1 简介
Flutter 是由谷歌开发推出的一款开源的移动应用开发框架,其主打的就是跨平台、高保真、高性能。其开发呢主要使用的dart语言。一套代码可以同时在Android和iOS 系统上运行。并且Flutter 还可以和原生进行混合开发。
2 主要优势
2.1跨平台自绘渲染引擎
Flutter 采用自绘渲染引擎Skia,那么和其他的跨平台框架不同的是,其他框架要不采用webView进行渲染,要不采用操作原生控件进行渲染, 并且避免了像RN,weex那样不断和native进行布局同步,导致的布局卡顿,手势卡顿等问题。那么Flutter利用自己的跨平台2d绘制渲染引擎,不仅可以保证UI的一致性,而且能降低维护的成本。(Skia是安卓系统一直使用的一个2d绘制渲染引擎)。
2.2 高性能
Flutter 支持了JIT(Just-In-Time 即时编译(动态编译)) 和 AOT (Ahead-of-time 静态编译)。
JIT 也就是程序在运行过程中实时将源码编翻译成机器码,也叫做动态编译,这种方式被称为JIT,典型的代表则为JavaScript。值得一提的是其实绝大多数的脚本语言都支持JIT。
AOT 需要提前将程序编译成机器码或者机器字节码,也叫做静态编译。比如我们OC、Swift、安卓的java、C、C++,都需要编译、链接之后才能运行程序。
那么Flutter既支持了JIT有支持了AOT,也就是说我们在开发的时候可以使用JIT进行快速开发,不需要保证应用的性能,可以做到随时改动、随时看到。在发布包的时候采用AOT,保证程序运行的新能。可以说鱼与熊掌兼得。
2.3 快速内存分配
Flutter 使用函数式流,而dart的开发成员很多是来自于Chrome的开发,鉴于Chrome V8的javaScript的内存分配,dart底层也拥有一个能够有效处理琐碎任务的内存分配器。
课外小知识–函数式流
函数式流通常指的是使用函数式编程范式来处理数据流和状态变化,函数式编程是一种编程范式,它强调使用纯函数(无副作用的函数)和不可变数据结构来编写程序。这种范式有助于提高代码的可读性、可维护性和可测试性。
函数式编程范式中的不可变数据结构和纯函数有助于减少内存分配。由于数据结构是不可变的,我们不需要为修改数据结构而分配新的内存。相反,我们可以通过共享不可变数据结构的部分来创建新的数据结构,从而减少内存分配。
2.4 类型安全和空安全
dart 语言是类型安全语言, 2.12版本支持了空安全。支持静态类型检测,所以可以再编译前发现一些类型的错误,排除一些错误。
课外小知识–空安全
空安全是一种编程语言的特性,指的是在帮助开发者避免空引用的错误,通常指试图访问一个空(null)对象的属性或者方法时,可能导致程序出现崩溃或者不可预期的错误。
2.5 dart 团队就在身边
因为dart团队和flutter团队同处于一个公司,那么对于Flutter想要dart支持的一些特性、功能dart团队也能快速沟通、快速实现。举例来说就是:最初dart团队并没有提供生成原生二进制的工具链,但是后边它实现了,因为dart团队专门为flutter开发了这一工具链。
3 Flutter框架结构
3.1 framework 层
framework层是完全有dart语言实现的sdk,它实现了一套基础库。从底向上分别是为
3.1.1 Foundation,Animation、Painting、Gestures
这一层又被称之为UI层。是Engine暴漏的底层UI层,主要提供了动画、绘制、手势。
3.3.2 Rendering
这一层是渲染层,渲染层会构建一颗由RenderObject组成的渲染树。当动态更新这些对象的时候,渲染树找出变化的部分,然后更新渲染。它是Flutter比较重要的部分,不仅要确定渲染对象的位置、大小还要进行坐标变换和绘制,当然绘制调用dart:UI层进行。
3.3.3 Widgets
Widgets是Flutter提供给我们一套基础组件库,提供了Material、Cupertino 两套分别符合安卓和iOS设计规范的组件。
值得一说的是,其实我们打交道最多的就是framework层,其中widgets是开发应用最多的,但是其中如果你有自定义(或者定制)的widgets,得需要Rendering层或者Painting。
3.2 Flutter Engine
这一层是Flutter的引擎层,其中包含了Skia渲染引擎、Dart运行时、以及文字排版引擎等。其使用C++实现。上一层的dart:UI最终会调用到这一层。
3.3 Embedder
嵌入层。嵌入层呢主要将我们的Flutter引擎安装到对应平台上。其实现是采用当前平台的原生语言实现。通过嵌入层,可以将flutter代码以模块方式嵌入到当前应用中,或者作为主体。
4 集成Flutter开发环境
4.1 开发IDE
推荐使用Android studio或者VS Code开发,官方提供了他们开发Flutter的插件,支持更多功能。
4.2 安装Flutter
具体查看Flutter中文网 或者Flutter 官网。照着做即可,不在赘述。
4.3 链接模拟器/真机
具体查看Flutter中文网 或者Flutter 官网。照着做即可,不在赘述。
5 Widgets
Flutter Widget 不同于我们原生开发所说的控件,Flutter 几乎所有的对象都叫做Widget,它不仅可以表示UI控件,也可以表示功能性组件,比如手势检测器GestureDetector。Widget只是UI信息配置信息的对象,并不是真正绘制到屏幕上的元素。
5.1 Flutter 中的四棵树
基本流程为:
- 根据Widget树生成Element树,每个Element节点类都继承于Element类;
- 根据Element树生成Render树(渲染树),渲染树中每个节点类都继承于RenderObject;
- 根据Render树生成Layer树,然后显示到屏幕上,Layer树中的每个节点类都继承于Layer类。
真正的渲染和布局是在Render树中,Element树是Widget和Render的桥梁。
举个栗子:
Container( // 一个容器 widget
color: Colors.blue, // 设置容器背景色
child: Row( // 可以将子widget沿水平方向排列
children: [
Image.network('https://www.example.com/1.png'), // 显示图片的 widget
const Text('A'),
],
),
);
其中Widget和Element是一一对应的,但是并不合Render一一对应。其实我们通常讲的Flutter只有上面三棵树,但是其实在上屏之前呢,还会根据Render树生成一颗Layer树。
5.2 StatelessWidget 和StatefulWidget 以及State
StatelessWidget 和 StatefulWidget都继承于Widget抽象类。
其中StatelessWidget 是用于不需要维护状态的场景,通常我们会在其override 方法 build(BuildContext context)里组合嵌套其他的组件构建UI。
StatefulWidget 是用于需要维护状态的场景,与StatelessWidget不同的是,我们不再拥有 build(BuildContext context)方法。取而代之的是createState()方法,我们需要生成一个State类,用来存储状态。
import 'package:flutter/material.dart';
class TestStatelessWidget extends StatelessWidget {
const TestStatelessWidget({super.key});
@override
Widget build(BuildContext context) {
return Container(color: Colors.blue, child: const Center(child: Text("StatelessWidget")));
}
}
class TestStatefulWidget extends StatefulWidget {
const TestStatefulWidget({super.key, this.count = 0});
final int count;
@override
TestWidgetState createState() => TestWidgetState();
}
class TestWidgetState extends State<TestStatefulWidget> {
int _count = 0;
// Widget 第一次插入Widget树调用,对于每一个State对象,只会调用一次
@override
void initState() {
super.initState();
_count = widget.count;
debugPrint("initState");
}
// 用于构建Widget
@override
Widget build(BuildContext context) {
debugPrint("build");
return Scaffold(
body: Center(
child: TextButton(
child: Text('$_count'),
onPressed: () => {
_count += 1,
}
)));
}
// Widget 重新构建时调用
@override
void didUpdateWidget(covariant TestStatefulWidget oldWidget) {
super.didUpdateWidget(oldWidget);
debugPrint("didUpdateWidget");
}
// State 对象从树中移除
@override
void deactivate() {
// TODO: implement deactivate
super.deactivate();
debugPrint("deactivate");
}
// 热重载时
@override
void reassemble() {
// TODO: implement reassemble
super.reassemble();
debugPrint("reassemble");
}
// 当state对象依赖发生变化时
@override
void didChangeDependencies() {
// TODO: implement didChangeDependencies
super.didChangeDependencies();
debugPrint("didChangeDependencies");
}
// 当state对象从树中永久移除时。
@override
void dispose() {
// TODO: implement dispose
super.dispose();
debugPrint("didChangeDependencies");
}
}
什么叫做状态?
在Flutter中,状态(State)是指应用程序中的数据,这些数据会随着时间的推移而发生变化。状态可以包括用户界面的外观、用户交互产生的数据、从服务器获取的数据等。状态的变化通常会导致应用程序的界面发生变化,例如按钮的颜色、文本的内容等。
在Flutter中,状态管理是一个重要的概念。由于Flutter使用声明式编程范式,组件(Widget)是不可变的,这意味着一旦创建,它们的属性就不能被修改。因此,当应用程序的状态发生变化时,我们需要创建一个新的组件实例来反映这些变化。
所以结合着以上所说的StatelessWidget和StatefullWidget、State想必大家就会有一个明确的认知State。
5.3 State状态管理(简述)
Flutter的状态管理分为以下几种:
- Widget 自身管理, 如上边的例子
- 父Widget管理子Widget状态;
- 混合管理,即组件自身管理一些内部状态,而父组件管理一些其他外部状态。
- 全局状态管理,用于跨组件/跨路由的状态管理
通常我们会采用专门用于状态的管理的三方包,进行简化管理,比如Bloc、Provider。(看情况出一个例子)
6 路由管理
所谓的路由管理,其实就是管理页面如何跳转,也称之为导航管理。那么对于Android和iOS客户端来说,都是维护一个路由栈,Push 入栈打开一个新页面, Pop出栈关闭页面。
6.1 通过Navigator 和 MaterialPageRoute 实现
// HomePageState
@override
Widget build(BuildContext context) {
return Scaffold(
... // 省略代码
body: Center(
child: Column(
... // 省略代码
TextButton(
onPressed: _routePageA,
child: const Text(
'打开pageA'
)
)
],
),
),
... // 省略代码
);
}
void _routePageA() async {
var result = await Navigator.push(
context,
MaterialPageRoute(builder: (context) => const PageA()
)
);
// 输出返回值
print(result.toString());
}
// PageA
@override
Widget build(BuildContext context) {
return Scaffold(
...
body: Center(
child: Column(children: [
...
CupertinoButton(
onPressed: () => _back(context),
child: const Text("返回"),
)
])
)
);
}
void _back(BuildContext context) {
Navigator.pop(context, "我是返回值");
}
6.2 通过路由表名
class MyApp extends StatelessWidget {
const MyApp({super.key});
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
...
routes: {
"pageA": (context) {
var arguments = ModalRoute.of(context)?.settings.arguments;
return PageA(title: arguments is String ? arguments : "");
},
"PageB": (context) => const PageB(),
"/": (context) => const MyHomePage(title: "首页")
},
initialRoute: "/", // 初始路由
);
}
}
// 使用
class _MyHomePageState extends State<MyHomePage> {
void _routePageB() async {
var result = await Navigator.pushNamed(context, "PageB");
debugPrint("我是PageA反传值:$result");
}
}
当然我们推荐使用路由表管理,因为1. 代码更好维护, 集中管理路由 2. 语义更加明确 3. 可以通过onGenerateRoute做路由跳转的一些前置处理。
7 包管理 & 资源管理
7.1 包管理
App 在实际开发中会依赖很多包,这些包通常可能会存在交叉依赖、版本依赖等。所以,这种开发生态都会出现一些包管理工具,比如Android使用Gradle来管理依赖,iOS 使用Cocoapods或者Carthage管理依赖。Flutter 也拥有自己包管理工具,它使用pubspec.yaml 文件管理。如:
name: flutter_in_action
description: First Flutter Application.
version: 1.0.0+1
dependencies:
flutter:
sdk: flutter
cupertino_icons: ^0.1.2
dev_dependencies:
flutter_test:
sdk: flutter
flutter:
uses-material-design: true
-
name
:应用或包名称。 -
description
: 应用或包的描述、简介。 -
version
:应用或包的版本号。 -
dependencies
:应用或包依赖的其他包或插件。 -
dev_dependencies
:开发环境依赖的工具包(而不是flutter应用本身依赖的包)。 -
flutter
:flutter相关的配置选项。7.1.1 依赖三方pub
官方Dart Package位于Pub(https://pub.dev/ ),我们可以在上面查找需要依赖的package以及版本号和package介绍。
依赖方式如下:
dependencies: flutter: sdk: flutter cupertino_icons: ^0.1.2 dio: ^5.3.2
然后执行:
dart pub get
或者直接在Android studio里上方点击Pub get即可7.1.2 依赖本地包/私有包
本地包:
dependencies: pkg1: path: ../../code/pkg1
git 包(package 位于根目录):
dependencies: pkg1: git: url: git://github.com/xxx/pkg1.git
git 包(package 位于非目录):
dependencies: pkg1: git: url: git://github.com/xxx/pkg1.git path: packages/package1
7.1.3 Flutter 包的分类
- dart 包: 纯dart编写的包,指依赖flutter框架
- 插件包:包含Dart编写的API, 以及特定平台的特定实现,也就是说插件包需要实现原生功能交互。比如
image_picker
实现了图片选取 + 照相功能。
7.2 资源管理
Flutter 资源管理也是在pubspec.yaml 文件里配置的。如下:
flutter:
# To add assets to your application, add an assets section, like this:
assets:
- images/2x/a_dot_burr.png
- images/3x/a_dot_burr.png
# 2x,3x代表分辨率
使用:
@override
Widget build(BuildContext context) {
// return Center(
// child: Image.asset("a_dot_burr.png"));
// 或者
return const Center(
child: Image(
image: AssetImage("a_dot_burr.png"))
);
}
8 组件简介
Flutter 中的组件大概分为下面几类:
- 基础组件
- Text、TextStyle等
- TextButton、ElevatedButton等
- …
- 布局类组件
- Row、Colum 线性布局
- Flex 弹性布局
- Wrap、Flow 流式布局
- Stack、Positioned 层叠布局
- Align 对齐与相对定位
- …
- 容器类组件
- Container 容器组件
- DecoratedBox 装饰容器
- Scaffold 页面脚手架
- ….
- 可滚动组件
- SingleChildScrollView
- ListView
- GridView
- PageView
- ….
- 功能性组件
- WillPopScope 导航返回拦截
- InheritedWidget 数据共享
- …
- 手势、事件处理和通知;动画;自定义组件,自行了解。
9 混合开发
一般情况下,我们不可能将现有的项目一下子都转化Flutter,只能循序渐进、逐渐的加入Flutter或者改写为Flutter,这个时候我们就需要考虑,以下几件事情:
-
怎么继承Flutter开发的模块
flutter 官网提供了 iOS 三种集成Flutter的方式:
- 使用 CocoaPods 依赖管理器安装 Flutter SDK 使用这种方法,每次构建应用的时候都会从源代码中编译
flutter_module
。(推荐) - 创建一个框架,把 Flutter 引擎、已编译的 Dart 代码和所有 Flutter 插件都放进去 这种方式你可以手动嵌入这个框架,并在 Xcode 中更改现有的应用的构建设置。如果不想要求开发团队的每一位成员都在本地安装 Flutter SDK 和 Cocoapods,这种方式比较适用
- 为已编译的 Dart 代码和所有 Flutter 插件创建一个框架,对 Flutter 引擎使用 CocoaPods 来管理 这种方式是将应用内容和插件作为内嵌的框架,但将 Flutter 引擎作为 CocoaPods podspec 分发。这有点类似第二种方式,但是它为分发大型的 Flutter.xcframework 文件提供了替代方案。
首先通过以下命令创建一个flutter_module
cd some/path/ flutter create --template module my_flutter
使用1.方式集成:
-
flutter 模块执行flutter build ios –debug / –release
-
Podfile 里加入一下代码
flutter_application_path = '../my_flutter' load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb') target 'MyApp' do install_all_flutter_pods(flutter_application_path) end post_install do |installer| flutter_post_install(installer) if defined?(flutter_post_install) end
- pod install 执行完毕后,打开.xcworkspace即可
使用2. 方法集成
在flutter module 下执行
flutter build ios-framework --output=some/path/MyApp/Flutter/
生成静态库嵌入到原生app中当做framework处理
使用3. 方法集成
使用以下命令生成Flutter.podspec
flutter build ios-framework --output=some/path/MyApp/Flutter/
采用Cocoapods集成:
pod 'Flutter', :podspec => 'some/path/MyApp/Flutter/[build mode]/Flutter.podspec'
安卓集成大概是两种:1. 生成aar 2. 依赖源码的模块,详细请移步到这里
iOS 详细内容请移步到这里
- 使用 CocoaPods 依赖管理器安装 Flutter SDK 使用这种方法,每次构建应用的时候都会从源代码中编译
-
如何进行Flutter和原生的互通
-
初始化需要初始化FlutterEngine以及注册Engine(安卓同学代码配置不一样,原理其实一致,详细移步到这里)
class AppDelegate: FlutterAppDelegate { lazy var flutterEngine = FlutterEngine(name: "My Flutter Engine") private var channel: FlutterMethodChannel! override func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { flutterEngine.run() GeneratedPluginRegistrant.register(with: self.flutterEngine) return super.application(application, didFinishLaunchingWithOptions: launchOptions) }
-
Flutter 在原生的承载页iOS 为FlutterViewController, 安卓为FlutterActivity(安卓同学代码配置不一样,原理其实一致,详细移步到这里)
比如我们原生直接采用以下方式打开Flutter
@objc func testButtonClicked() { let flutterEngine = (UIApplication.shared.delegate as! AppDelegate).flutterEngine let flutterViewController = FlutterViewController(engine: flutterEngine, nibName: nil, bundle: nil) present(flutterViewController, animated: true) }
-
双方互通采用Channel通道进行
- Channel 分为三种 FlutterBasicMessageChannel、FlutterMethodChannel、FlutterEventChannel
BasicMessageChannel
用于双向通信,支持发送和接收任意类型的消息,通信模式是无状态的。MethodChannel
用于远程过程调用,允许Dart代码调用原生代码中的方法,通信模式是有状态的。EventChannel
用于事件流通信,允许原生代码向Dart代码发送事件,通信模式是基于流的。
class AppDelegate: FlutterAppDelegate { lazy var flutterEngine = FlutterEngine(name: "My Flutter Engine") private var channel: FlutterMethodChannel! override func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { flutterEngine.run() GeneratedPluginRegistrant.register(with: self.flutterEngine) channel = FlutterMethodChannel(name: "flutter_channel", binaryMessenger: self.flutterEngine.binaryMessenger) channel.setMethodCallHandler { call, result in if call.method == "getBattery" { result(self.getBattery()) } else { result(nil) } } channel.invokeMethod("invokeFlutterMethod", arguments: "我是原生") { result in print(result ?? "没调用到") } return super.application(application, didFinishLaunchingWithOptions: launchOptions) } func getBattery() -> String { UIDevice.current.isBatteryMonitoringEnabled = true return "您的电量为: \(-UIDevice.current.batteryLevel * 100) %" } }
-
-
多Flutter实例
比如NativePage->flutterpage ->nativePage->flutterPage的情况。
Flutter 2 以及以上的版本针对多 Flutter 实例进行了优化,额外增加的 Flutter 实例只会增加约 180K 的内存占用,这种「固定成本」的降低,可以帮助你更轻松的将 Flutter 加入到现有应用 (add-to-app)
主要采用新的API FlutterEngineGroup 去创建和管理多个Engine,从
FlutterEngineGroup
生成的FlutterEngine
具有常用共享资源(例如 GPU 上下文、字体度量和隔离线程的快照)的性能优势,从而加快首次渲染的速度、降低延迟并降低内存占用。据说新的方式可以降低内存,那么在2.0之前,我们一直采用的是三方混合路由管理方案如:flutter_boost(咸鱼), 采用单例Engine共用的方式,减小内存。
例如:
class AppDelegate: FlutterAppDelegate { let engines = FlutterEngineGroup(name: "multiple-flutters", project: nil) ... } // 一个页面一个Flutter实例 class SingleFlutterViewController: FlutterViewController { private var channel: FlutterMethodChannel! init(withEntrypoint entryPoint: String?) { let appDelegate = UIApplication.shared.delegate as! AppDelegate let newEngine = appDelegate.engines.makeEngine(withEntrypoint: entryPoint, libraryURI: nil) GeneratedPluginRegistrant.register(with: newEngine) super.init(engine: newEngine, nibName: nil, bundle: nil) } override func viewDidLoad() { super.viewDidLoad() channel = FlutterMethodChannel(name: "multiple-flutters", binaryMessenger: self.engine!.binaryMessenger) channel.invokeMethod("setCount", arguments: nil) channel.setMethodCallHandler { call, result in if call.method == "next" { let vc = HostViewController() self.navigationController!.pushViewController(vc, animated: true) result(nil) } else { result(nil) } } } // 一个页面多个Flutter实例 class DoubleFlutterViewController: UIViewController { private let topFlutter: SingleFlutterViewController = SingleFlutterViewController( withEntrypoint: "topMain") private let bottomFlutter: SingleFlutterViewController = SingleFlutterViewController( withEntrypoint: "bottomMain") override func viewDidLoad() { addChild(topFlutter) addChild(bottomFlutter) let safeFrame = self.view.safeAreaLayoutGuide.layoutFrame let halfHeight = safeFrame.height / 2.0 topFlutter.view.frame = CGRect( x: safeFrame.minX, y: safeFrame.minY, width: safeFrame.width, height: halfHeight) bottomFlutter.view.frame = CGRect( x: safeFrame.minX, y: topFlutter.view.frame.maxY, width: safeFrame.width, height: halfHeight) self.view.addSubview(topFlutter.view) self.view.addSubview(bottomFlutter.view) topFlutter.didMove(toParent: self) bottomFlutter.didMove(toParent: self) } } // 路由栈中多个Flutter实例 extension HostViewController { @objc func nextButtonClicked() { let navController = self.navigationController! if navController.viewControllers.count % 4 == 0 { let vc = SingleFlutterViewController(withEntrypoint: nil) navController.pushViewController(vc, animated: true) } else { let vc = DoubleFlutterViewController() navController.pushViewController(vc, animated: true) } } }
学习 & 参考文档