介绍

Flutter是谷歌的移动UI框架,可以快速在iOS和Android上构建高质量的原生用户界面。2015年5月推出,使用Dart语言。

一些知识点

Dart 语言开发文档: https://dart.cn/guides
Flutter中文网: https://flutterchina.club/docs/
Pub Packages: https://pub.flutter-io.cn/
Flutter示例项目RoamCatF: https://github.com/guangGG/RoamCatFlutter

代码风格和语言基础知识(列出和Java相比特殊的部分)

类文件名格式: a_b_c.dart [lowercase_with_underscores]
常量格式: aBcDe [lowerCamelCase]
文档注释推荐使用 /// ,不推荐使用 /*/
未初始化的数字变量默认的初始化值是null
int表示64位整形数字,double表示64位的双精度浮点数字(无long和float类型)
可以使用单引号或者双引号来创建字符串;
可以在字符串中以 ${表达式} 的形式使用表达式,如果表达式是一个标识符,可以省略掉 {};
两个字符串可以用 == 运算符判断内容是否一样;

mixin (混入)

1
2
3
参考:https://juejin.im/post/5bb204d3e51d450e4f38e2f6
在Dart语言中,mixin相当于实现多重继承的功能;可以使用with关键字“继承”多个mixin;
类似于implement接口,但可以在mixin中写直接方法的实现,不需要在多个子类中重复写实现;

State 和 Provider (flutter页面状态管理)

1
2
3
4
5
6
StatelessWidget:页面元素StatelessElement创建后就固定了
StatefulWidget:通过setState方法更新页面元素

Provider的用法
源码:https://github.com/rrousselGit/provider
参考:https://www.jianshu.com/p/36372e0bfb49

Platform Channel (原生和flutter间传递数据)

参考:https://juejin.im/post/5b84ff6a6fb9a019f47d1cc9
Channel作用域:FlutterView,安卓下是在一个FlutterActivity中传递及处理数据
备注:registrarFor(“$name”).messenger()在新的embedding包中换成flutterEngine.dartExecutor.binaryMessenger

BasicMessageChannel:用于传递自定义格式的信息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
///Android代码
//创建BasicMessageChannel对象并设置数据解析器MessageCodec (这里用String格式为例)
val basicMessageChannel = BasicMessageChannel(registrarFor("$ChannelPlugin").messenger(),
"$ChannelPlugin", object : MessageCodec<String> {
override fun encodeMessage(message: String?): ByteBuffer? {
return ByteBuffer.wrap(message?.toByteArray())
}

override fun decodeMessage(message: ByteBuffer?): String? {
return if (message?.array() == null) {
null
} else {
String(message.array()!!)
}
}
})
//注册flutter发送数据的接收器
basicMessageChannel.setMessageHandler(object : BasicMessageChannel.MessageHandler<String> {
override fun onMessage(message: String?, reply: BasicMessageChannel.Reply<String>) {
//deal message
reply.reply("$result")
}
})
//向flutter发送数据
basicMessageChannel.send("$msg", object : BasicMessageChannel.Reply<String> {
override fun reply(reply: String?) {
//deal reply
}
})
///flutter代码
//创建BasicMessageChannel对象并设置数据解析器MessageCodec(这里用String格式为例)
var basicMessageChannel = BasicMessageChannel("$ChannelPlugin", StringCodec());
//注册原生发送数据的接收器
basicMessageChannel.setMessageHandler((msg) async {
//result = deal msg
return result;
});
//向原生发送数据
var result = await basicMessageChannel.send("$message");
MethodChannel:flutter和原生双向方法调用,允许数据传入和回传
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
注:method为字符串,请求arguments和回调resultData一般可定义成Map或JSONObject
1.flutter调用原生场景
///flutter代码
var result = await MethodChannel("$MethodChannelPlugin").invokeMethod("$method", arguments);
///Android代码,接收到请求参数处理后回调处理结果,然后在flutter中异步获取处理结果即可
MethodChannel(registrarFor("$MethodChannelPlugin").messenger(), "$MethodChannelPlugin")
.setMethodCallHandler(object : MethodChannel.MethodCallHandler {
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
//"method=" + call.method + ",args=" + call.arguments
//...deal
//result.success(mutableMapOf(Pair("key1", "value1"), Pair("key2", "value2")))
//result.error("$code", "$msg", errorDetails)
}
})
2.原生调用flutter场景
///flutter代码,注册回调处理器,处理完的结果异步返回
MethodChannel("$MethodChannelPlugin").setMethodCallHandler((MethodCall call) async {
//"method=" + call.method + ",args=" + call.arguments
//...deal
var data = <String, String>{"key1": "value1", "key2": "value2"};
return data;
});
///Android代码
val channel = MethodChannel(registrarFor("$MethodChannelPlugin").messenger(), "$MethodChannelPlugin")
channel.invokeMethod("$method", arguments, object : MethodChannel.Result {
override fun success(result: Any?) {
}

override fun error(errorCode: String?, errorMessage: String?, errorDetails: Any?) {
}

override fun notImplemented() {
}
})
EventChannel: 原生向flutter发送event,无数据回传
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//flutter代码
EventChannel('EventChannelPlugin').receiveBroadcastStream(arguments).listen(
(data) {...接收原生消息...},
onDone: () {},
onError: (args) {},
);
//Android代码
var eventSink: EventChannel.EventSink? = null //成员变量(可以是全局的)
EventChannel(registrarFor("EventChannelPlugin").messenger(), "EventChannelPlugin")
.setStreamHandler(object : EventChannel.StreamHandler {
override fun onListen(arguments: Any?, events: EventChannel.EventSink?) {
eventSink = events
}
override fun onCancel(arguments: Any?) {
EventGlobalPlugin.eventSink = null
}
})
//需要发送event到flutter时,直接用接收到的EventSink发送消息即可
eventSink?.success(mutableMapOf(Pair("key1", "value1"), Pair("key2", "value2")))
eventSink?.error("$code", "$msg", obj)
webview_flutter库封装的Channel(JS和flutter间传递数据)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//在WebView构造方法中配置javascriptChannels
WebView(
...,
javascriptChannels: <JavascriptChannel>[
JavascriptChannel(
name: 'NativeObj',
onMessageReceived: (JavascriptMessage jsMsg) async {
//jsMsg.message即为从js传到flutter的string消息;
//可以定义成json格式,通过解析内部字段做响应处理;
//这种方式只能实现js向flutter的单项数据传输,没有回调;
//需要回调时可以使用下面的evaluateJavascript做处理;
}),
...,
].toSet(),
onWebViewCreated: (WebViewController controller) {
//WebView创建成功时获取到WebViewController
}
...,
)
//flutter可以通过WebViewController的evaluateJavascript调用JS中的方法,例如:
controller.evaluateJavascript("document.cookie='key=$value;domain=$domain;path=/;'");

原有安卓项目集成flutter-Module

1
2
3
4
5
6
7
8
9
参考:https://cloud.tencent.com/developer/news/577681
1.需要把项目的主module名字改成app,否则编译报错:Cause: assert appProject != null
2.通过new-Module方式创建一个Flutter-Module(默认会在settings.gradle和主module的build.gradle引入)
3.在项目APP入口初始化Flutter:FlutterMain.startInitialization(app)
4.新建一个Activity,继承自FlutterActivity,这个Activity就可以显示flutter-Module中的页面了,activity注册标签中配置使用原生Activity的启动背景(不配置会短暂黑屏):
<meta-data
android:name="io.flutter.app.android.SplashScreenUntilFirstFrame"
android:value="true" />
5.项目clone到新电脑上时,需要在菜单:File | Settings | Languages & Frameworks | Dart/Flutter中配置好SDK,然后在pubspec.yaml文件中"Packages get"下载依赖,然后再"Sync Gradle"

开发及导入Packages和插件

开发自定义插件并在pubspec.yaml中引入(发布到Pub的方法请看官方文档)。

1
2
3
4
5
6
7
8
9
文档:https://flutterchina.club/developing-packages/
开发Package:
1.AndroidStudio中"New"--"New Flutter Project"--"Flutter Package",输入名称、描述、父目录地址,创建Package;
2.在lib目录下编写逻辑;
3.在其他工程引入(比如相对路径方式):"../pkgname/"、"./dir/pkgname"
开发插件(带原生交互的Package):
1.AndroidStudio中"New"--"New Flutter Project"--"Flutter Plugin",输入名称、描述、父目录地址、包名、其他配置信息,创建Plugin;
2.在lib/android/ios目录编写逻辑;
3.在其他工程引入(比如相对路径方式):"../pkgname/"、"./dir/pkgname"

国际化

推荐使用flutter_intl插件(在AndroidStudio插件设置中搜索并安装’Flutter Intl’插件),具体使用方法见:https://juejin.im/post/5e4536d0e51d4526ef5f85a9

状态管理器(provider-bloc)

依赖库源码见:https://github.com/felangel/bloc/tree/master/packages/flutter_bloc
大概流程:
1.自定义Bloc,包括 Event 和 State,接收到指定Event时对State作相应操作,并在Stream中yield出去;
2.对于全局的状态,在main函数中将 MultiBlocProvider 设置为AppWidget,并配置全局的providers;
3.在需要改变全局状态的页面中initState时获取到Bloc对象: _assignBloc = context.bloc(); 然后在对应操作时发送对应event: assignBloc.add(event);
4.在需要展示状态的Widget中,使用BlocBuilder<AssignBloc, AssignState>(builder: (context, state) { return _widgetOf(state); }) 包裹Widget,即可实时监听AssignState的全局状态变化并展示;
备注:全局的Bloc用法类似于vuex,多个界面实时监听state的变化,通过event改变state会触发界面的更新;主要用于多页面共享数据的处理,如果是单个页面内逻辑简单的数据处理和展示,推荐仍使用setState((){})方式处理。

页面路由-Route

MaterialApp配置 routes 或 onGenerateRoute 来设定RouteSettings与指定的PageRoute的对应关系;
其中settings.name为页面唯一标识,settings.arguments为页面传参;
MaterialApp配置 initialRoute 来指定启动页(效果和通过home指定启动页Widget功能一样);

页面跳转的几种方式:

Navigator.of(context).pushNamed 在当前页面上层打开指定新页面,展示新页面打开动画
Navigator.of(context).pushReplacementNamed 当前页面替换为指定新页面,当前页面会关闭,展示新页面打开动画
Navigator.of(context).popAndPushNamed 当前页面替换为指定新页面,当前页面会关闭,展示当前页面的关闭动画和新页面打开动画
Navigator.of(context).pushNamedAndRemoveUntil 打开指定新页面,并逐个移除当前页面栈中的页面直到predicate配置为true的页面,直接返回false表示全部移除当前页面栈

页面移除的几种方式:

Navigator.of(context).pop 移除当前页面,展示当前页面的关闭动画
Navigator.of(context).maybePop 如果页面可以移除时(见canPop方法)则移除当前页面,否则不处理页面移除操作
Navigator.of(context).popUntil 逐个移除当前页面栈中的页面直到predicate配置为true的页面,默认全部移除当前页面栈
Navigator.of(context).popAndPushNamed (是一种页面跳转方式)移除当前页面,打开指定新页面,展示当前页面的关闭动画和新页面打开动画

页面数据传递

Navigator.push方法可通过arguments传入数据到下一个页面,方法返回值为下一个页面返回数据封装的Future;
Navigator.pop方法可以直接传入数据,在上个页面的push方法中异步返回

其他

Navigator.of(context).canPop() 判断页面是否可以移除,判断条件为页面栈数量多于1个或route.willHandlePopInternally属性为true
AppBar 的 automaticallyImplyLeading 属性置为false时不会显示页面标题栏上的返回按钮
PopupRoute: 弹出路由,用于展示 PopupMenu 、 Dialog 这些弹窗类界面
PopupRoute通过Navigator.of(context).push(Route)方式弹出,使用pop方法关闭弹窗

自动化生成json格式对象类

引入库: https://pub.flutter-io.cn/packages/build_runner

1
2
3
4
5
6
dev_dependencies:
build_runner: ^1.8.1
1.在网上工具把json字符串粘贴后生成xxx.dart文件内容
https://caijinglong.github.io/json2dart/index_ch.html
2.在应用根目录命令行输入指令自动生成xxx.g.dart:
'flutter packages pub run build_runner build --delete-conflicting-outputs'