【Flutter 混合开发】嵌入原生View-Android
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 regist 56c erWith(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: platfor ad0 mView(), ), ); } }
上面嵌入的是 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( 56c 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']}'; ad8 }); }, ),
解决多个原生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:view 1044 Id,使用 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】:
- Flutter混合开发——探索一种另类却高效的的原生View嵌入方法
- Android H5混合开发(3):原生Android项目里嵌入Cordova
- Android笔记:在Flutter中嵌入原生View
- android混合开发,webview的java与js互操作
- Android混合开发之WebViewJavascriptBridge实现JS与java安全交互
- Android开发: 使用WebView来进行混合开发
- Android混合开发之WebView与Js交互
- Flutter完整开发实战详解(二十、 Android PlatformView 和键盘问题)
- android h5 js 混合开发解决webview加载白屏,不加载网页的问题(硬件加速网页渲染,优化体验)
- Android混合开发之WebView与Javascript交互
- Android混合开发之WebView与Javascript交互
- 跨平台技术篇 - 使用 Flutter 与原生技术混合开发示例
- Android开发之Webview中原生与JS交互
- Android原生软件里嵌入一个基于cordova的网页页面(webview)
- 混合开发之webView加载html,android 和 html之间进行数据交互
- 最详细的Android原生APP中添加ReactNative 进行混合开发教程
- Android混合开发之WebView使用总结
- Android混合开发之WebViewJavascriptBridge实现JS与java安全交互
- Android混合开发之WebViewJavascriptBridge实现JS与java安全交互
- 不得不看的Flutter与Android混合开发