介绍
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
|
参考: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'
|