Flutter 混合开发系列 包含如下:
- 嵌入原生View-Android
- 嵌入原生View-IOS
- 与原生通信-MethodChannel
- 与原生通信-BasicMessageChannel
- 与原生通信-EventChannel
- 添加 Flutter 到 Android Activity
- 添加 Flutter 到 Android Fragment
- 添加 Flutter 到 iOS
每个工作日分享一篇,欢迎关注、点赞及转发。
@H_403_30@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,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,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',); } } 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,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,年龄:$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','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',body: Column(children: [ Row( children: [ RaisedButton( child: Text('传递参数给原生View'),onPressed: () { platform .invokeMethod('setText','age': 18}); },RaisedButton( child: Text('$_data'),onPressed: () async { var result = await platform .invokeMethod('getData','age': 18}); setState(() { _data = '${result['name']},${result['age']}'; }); },],Expanded(child: Container(color: Colors.red,child: platformView())),Expanded(child: Container(color: Colors.blue,Expanded(child: Container(color: Colors.yellow,); } }
此时点击 传递参数给原生View 按钮哪个View会改变内容,实际上只有最后一个会改变。
如何改变指定View的内容?重点是 MethodChannel,只需修改上面3个通道的名称不相同即可:
- 第一种方法:将一个唯一 id 通过初始化参数传递给原生 View,原生 View使用这个id 构建不同名称的 MethodChannel。
- 第二种方法(推荐):原生 View 生成时,系统会为其生成唯一id:viewId,使用 viewId 构建不同名称的 MethodChannel。
原生 View 使用 viewId 构建不同名称的 MethodChannel:
class MyFlutterView(context: Context,"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')); },)
给第一个发送消息:
platforms[0] .invokeMethod('setText','age': 18});
交流
老孟Flutter博客(330个控件用法+实战入门系列文章):http://laomengit.com
欢迎加入Flutter交流群(微信:laomengit)、关注公众号【老孟Flutter】: