Flutter 混合开发系列 包含如下:
与原生通信-BasicMessageChannel
添加 Flutter 到 Android Activity
添加 Flutter 到 Android Fragment
每个工作日分享一篇,欢迎关注、点赞及转发。
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。
注册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 编码后再发送给平台侧,它应该与传递给构造函数的编解码器匹配。值的范围:
修改 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 });