Flutter 混合开发系列 蕴含如下:
- 嵌入原生View-Android
- 嵌入原生View-IOS
- 与原生通信-MethodChannel
- 与原生通信-BasicMessageChannel
- 与原生通信-EventChannel
- 增加 Flutter 到 Android Activity
- 增加 Flutter 到 Android Fragment
- 增加 Flutter 到 iOS
每个工作日分享一篇,欢送关注、点赞及转发。
AndroidView
倡议应用 Android Studio 进行开发,在 Android Studio 左侧 project tab下选中 android 目录下任意一个文件,右上角会呈现 Open for Editing in Android Studio ,
点击即可关上,关上后 project tab 并不是一个 Android 我的项目,而是我的项目中所有 Android 我的项目,蕴含第三方:
app 目录是以后我的项目的 android 目录,其余则是第三方的 android 目录。
在App 我的项目的 java/包名 目录下创立嵌入 Flutter 中的 Android View,此 View 继承 PlatformView :
class MyFlutterView(context: Context) : PlatformView { override fun getView(): View { TODO("Not yet implemented") } override fun dispose() { TODO("Not yet implemented") }}
- getView :返回要嵌入 Flutter 层次结构的Android View
- dispose:开释此View时调用,此办法调用后 View 不可用,此办法须要革除所有对象援用,否则会造成内存透露。
返回一个简略的 TextView :
class MyFlutterView(context: Context, messenger: BinaryMessenger, viewId: Int, args: Map<String, Any>?) : PlatformView { val textView: TextView = TextView(context) init { textView.text = "我是Android View" } override fun getView(): View { return textView } override fun dispose() { TODO("Not yet implemented") }}
- messenger:用于消息传递,前面介绍 Flutter 与 原生通信时用到此参数。
- viewId:View 生成时会调配一个惟一 ID。
- args:Flutter 传递的初始化参数。
注册PlatformView
创立PlatformViewFactory:
class MyFlutterViewFactory(val messenger: BinaryMessenger) : PlatformViewFactory(StandardMessageCodec.INSTANCE) { override fun create(context: Context, viewId: Int, args: Any?): PlatformView { val flutterView = MyFlutterView(context, messenger, viewId, args as Map<String, Any>?) return flutterView }}
创立 MyPlugin :
class MyPlugin : FlutterPlugin { override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) { val messenger: BinaryMessenger = binding.binaryMessenger binding .platformViewRegistry .registerViewFactory( "plugins.flutter.io/custom_platform_view", MyFlutterViewFactory(messenger)) } companion object { @JvmStatic fun registerWith(registrar: PluginRegistry.Registrar) { registrar .platformViewRegistry() .registerViewFactory( "plugins.flutter.io/custom_platform_view", MyFlutterViewFactory(registrar.messenger())) } } override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) { }}
记住 plugins.flutter.io/custom_platform_view ,这个字符串在 Flutter 中须要与其保持一致。
在 App 中 MainActivity 中注册:
class MainActivity : FlutterActivity() { override fun configureFlutterEngine(flutterEngine: FlutterEngine) { super.configureFlutterEngine(flutterEngine) flutterEngine.plugins.add(MyPlugin()) }}
如果是 Flutter Plugin,没有MainActivity,则在对应的 Plugin onAttachedToEngine 和 registerWith 办法批改如下:
public class CustomPlatformViewPlugin : FlutterPlugin,MethodCallHandler { /// The MethodChannel that will the communication between Flutter and native Android /// /// This local reference serves to register the plugin with the Flutter Engine and unregister it /// when the Flutter Engine is detached from the Activity private lateinit var channel: MethodChannel override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { channel = MethodChannel(flutterPluginBinding.getFlutterEngine().getDartExecutor(), "custom_platform_view") channel.setMethodCallHandler(this) val messenger: BinaryMessenger = flutterPluginBinding.binaryMessenger flutterPluginBinding .platformViewRegistry .registerViewFactory( "plugins.flutter.io/custom_platform_view", MyFlutterViewFactory(messenger)) } // This static function is optional and equivalent to onAttachedToEngine. It supports the old // pre-Flutter-1.12 Android projects. You are encouraged to continue supporting // plugin registration via this function while apps migrate to use the new Android APIs // post-flutter-1.12 via https://flutter.dev/go/android-project-migration. // // It is encouraged to share logic between onAttachedToEngine and registerWith to keep // them functionally equivalent. Only one of onAttachedToEngine or registerWith will be called // depending on the user's project. onAttachedToEngine or registerWith must both be defined // in the same class. companion object { @JvmStatic fun registerWith(registrar: Registrar) { val channel = MethodChannel(registrar.messenger(), "custom_platform_view") channel.setMethodCallHandler(CustomPlatformViewPlugin()) registrar .platformViewRegistry() .registerViewFactory( "plugins.flutter.io/custom_platform_view", MyFlutterViewFactory(registrar.messenger())) } } override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) { if (call.method == "getPlatformVersion") { result.success("Android ${android.os.Build.VERSION.RELEASE}") } else { result.notImplemented() } } override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) { channel.setMethodCallHandler(null) }}
嵌入Flutter
在 Flutter 中调用
class PlatformViewDemo extends StatelessWidget { @override Widget build(BuildContext context) { Widget platformView(){ if(defaultTargetPlatform == TargetPlatform.android){ return AndroidView( viewType: 'plugins.flutter.io/custom_platform_view', ); } } return Scaffold( appBar: AppBar(), body: Center( child: platformView(), ), ); }}
下面嵌入的是 Android View,因而通过 defaultTargetPlatform == TargetPlatform.android 判断以后平台加载,在 Android 上运行成果:
设置初始化参数
Flutter 端批改如下:
AndroidView( viewType: 'plugins.flutter.io/custom_platform_view', creationParams: {'text': 'Flutter传给AndroidTextView的参数'}, creationParamsCodec: StandardMessageCodec(), )
- creationParams :传递的参数,插件能够将此参数传递给 AndroidView 的构造函数。
creationParamsCodec :将 creationParams 编码后再发送给平台侧,它应该与传递给构造函数的编解码器匹配。值的范畴:
- StandardMessageCodec
- JSONMessageCodec
- StringCodec
- BinaryCodec
批改 MyFlutterView :
class MyFlutterView(context: Context, messenger: BinaryMessenger, viewId: Int, args: Map<String, Any>?) : PlatformView { val textView: TextView = TextView(context) init { args?.also { textView.text = it["text"] as String } } override fun getView(): View { return textView } override fun dispose() { TODO("Not yet implemented") }}
最终成果:
Flutter 向 Android View 发送音讯
批改 Flutter 端,创立 MethodChannel 用于通信:
class PlatformViewDemo extends StatefulWidget { @override _PlatformViewDemoState createState() => _PlatformViewDemoState();}class _PlatformViewDemoState extends State<PlatformViewDemo> { static const platform = const MethodChannel('com.flutter.guide.MyFlutterView'); @override Widget build(BuildContext context) { Widget platformView() { if (defaultTargetPlatform == TargetPlatform.android) { return AndroidView( viewType: 'plugins.flutter.io/custom_platform_view', creationParams: {'text': 'Flutter传给AndroidTextView的参数'}, creationParamsCodec: StandardMessageCodec(), ); } } return Scaffold( appBar: AppBar(), body: Column(children: [ RaisedButton( child: Text('传递参数给原生View'), onPressed: () { platform.invokeMethod('setText', {'name': 'laomeng', 'age': 18}); }, ), Expanded(child: platformView()), ]), ); }}
在 原生View 中也创立一个 MethodChannel 用于通信:
class MyFlutterView(context: Context, messenger: BinaryMessenger, viewId: Int, args: Map<String, Any>?) : PlatformView, MethodChannel.MethodCallHandler { val textView: TextView = TextView(context) private var methodChannel: MethodChannel init { args?.also { textView.text = it["text"] as String } methodChannel = MethodChannel(messenger, "com.flutter.guide.MyFlutterView") methodChannel.setMethodCallHandler(this) } override fun getView(): View { return textView } override fun dispose() { methodChannel.setMethodCallHandler(null) } override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) { if (call.method == "setText") { val name = call.argument("name") as String? val age = call.argument("age") as Int? textView.text = "hello,$name,年龄:$age" } else { result.notImplemented() } }}
Flutter 向 Android View 获取音讯
与下面发送信息不同的是,Flutter 向原生申请数据,原生返回数据到 Flutter 端,批改 MyFlutterView onMethodCall:
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) { if (call.method == "setText") { val name = call.argument("name") as String? val age = call.argument("age") as Int? textView.text = "hello,$name,年龄:$age" } else if (call.method == "getData") { val name = call.argument("name") as String? val age = call.argument("age") as Int? var map = mapOf("name" to "hello,$name", "age" to "$age" ) result.success(map) } else { result.notImplemented() }}
result.success(map) 是返回的数据。
Flutter 端接收数据:
var _data = '获取数据';RaisedButton( child: Text('$_data'), onPressed: () async { var result = await platform .invokeMethod('getData', {'name': 'laomeng', 'age': 18}); setState(() { _data = '${result['name']},${result['age']}'; }); },),
解决多个原生View通信抵触问题
当然页面有3个原生View,
class PlatformViewDemo extends StatefulWidget { @override _PlatformViewDemoState createState() => _PlatformViewDemoState();}class _PlatformViewDemoState extends State<PlatformViewDemo> { static const platform = const MethodChannel('com.flutter.guide.MyFlutterView'); var _data = '获取数据'; @override Widget build(BuildContext context) { Widget platformView() { if (defaultTargetPlatform == TargetPlatform.android) { return AndroidView( viewType: 'plugins.flutter.io/custom_platform_view', creationParams: {'text': 'Flutter传给AndroidTextView的参数'}, creationParamsCodec: StandardMessageCodec(), ); } } return Scaffold( appBar: AppBar(), body: Column(children: [ Row( children: [ RaisedButton( child: Text('传递参数给原生View'), onPressed: () { platform .invokeMethod('setText', {'name': 'laomeng', 'age': 18}); }, ), RaisedButton( child: Text('$_data'), onPressed: () async { var result = await platform .invokeMethod('getData', {'name': 'laomeng', 'age': 18}); setState(() { _data = '${result['name']},${result['age']}'; }); }, ), ], ), Expanded(child: Container(color: Colors.red, child: platformView())), Expanded(child: Container(color: Colors.blue, child: platformView())), Expanded(child: Container(color: Colors.yellow, child: platformView())), ]), ); }}
此时点击 传递参数给原生View 按钮哪个View会扭转内容,实际上只有最初一个会扭转。
如何扭转指定View的内容?重点是 MethodChannel,只需批改下面3个通道的名称不雷同即可:
- 第一种办法:将一个惟一 id 通过初始化参数传递给原生 View,原生 View应用这个id 构建不同名称的 MethodChannel。
- 第二种办法(举荐):原生 View 生成时,零碎会为其生成惟一id:viewId,应用 viewId 构建不同名称的 MethodChannel。
原生 View 应用 viewId 构建不同名称的 MethodChannel:
class MyFlutterView(context: Context, messenger: BinaryMessenger, viewId: Int, args: Map<String, Any>?) : PlatformView, MethodChannel.MethodCallHandler { val textView: TextView = TextView(context) private var methodChannel: MethodChannel init { args?.also { textView.text = it["text"] as String } methodChannel = MethodChannel(messenger, "com.flutter.guide.MyFlutterView_$viewId") methodChannel.setMethodCallHandler(this) } ...}
Flutter 端为每一个原生 View 创立不同的MethodChannel:
var platforms = [];AndroidView( viewType: 'plugins.flutter.io/custom_platform_view', onPlatformViewCreated: (viewId) { print('viewId:$viewId'); platforms .add(MethodChannel('com.flutter.guide.MyFlutterView_$viewId')); }, creationParams: {'text': 'Flutter传给AndroidTextView的参数'}, creationParamsCodec: StandardMessageCodec(),)
给第一个发送音讯:
platforms[0] .invokeMethod('setText', {'name': 'laomeng', 'age': 18});
交换
老孟Flutter博客(330个控件用法+实战入门系列文章):http://laomengit.com
欢送退出Flutter交换群(微信:laomengit)、关注公众号【老孟Flutter】: