美文网首页
【Flutter 混合开发】嵌入原生View-Android

【Flutter 混合开发】嵌入原生View-Android

作者: 老孟程序员 | 来源:发表于2020-10-14 06:45 被阅读0次

    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 混合开发】嵌入原生View-Android

          本文链接:https://www.haomeiwen.com/subject/abkipktx.html