美文网首页
Flutter中嵌入Android控件(View)

Flutter中嵌入Android控件(View)

作者: 信仰年輕 | 来源:发表于2021-06-08 18:37 被阅读0次

    版本
    Flutter版本2.0.3
    GitHub源代码

    本文目标

    成功在Flutter页面中嵌入Android原生控件

    基本步骤

    • 准备Native UI的四件套
    • 注册Native UI
    • 准备flutter/platform_views
    • Dart侧对应widget封装

    准备Native UI的四件套

    要想将Native UI(view) 嵌入到Flutter中,首先我们需要准备Native UI的四件套,来一起看下什么是Native UI的四件套

    • PlatformView:要嵌入到Flutter的iOS view 或 Android view
    • PlatformViewController:PlatformView的控制器,用来创建和管理PlatformView
    • PlatformViewFactory:用于向Flutter提供PlatformView
    • PlatformViewPlugin:用于向Flutter注册PlatformView
      对于Flutter而言,无论是Android 还是iOS将Native UI(View)嵌入到Flutter中使用的原理和步骤是相同的,接下来我们以Android为例子来将ImageView控件嵌入到Flutter页面中进行使用

    准备PlatformView

    class FImageView @JvmOverloads constructor(
        context: Context?,
        attrs: AttributeSet? = null,
        defStyleAttr: Int = 0
    ) :
        AppCompatImageView(context, attrs, defStyleAttr) {
    
        fun setUrl(url: String) {
            Glide.with(this).load(url).into(this)
        }
    }
    

    这里,我们使用Glide进行图片的加载,大家可以根据需要选择适合自己的图片加载库

    准备PlatformViewController

    class FImageViewController(context: Context, messenger: BinaryMessenger, id: Int?, args: Any?) :
        PlatformView, MethodChannel.MethodCallHandler {
    
        private val imageView: FImageView = FImageView(context)
        private var methodChannel: MethodChannel
    
        init {
            //通信
            methodChannel = MethodChannel(messenger, "FImageView_$id")
            methodChannel.setMethodCallHandler(this)
            if (args is Map<*, *>) {
                imageView.setUrl(args["url"] as String)
            }
            print(args)
        }
        
        override fun getView(): View {
            return imageView
        }
    
        override fun dispose() {
        }
    
        //通信flutter调用原生
        override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
            when (call.method) {
                "setUrl" -> {
                    val url = call.argument<String>("url")
                    if (url != null) {
                        imageView.setUrl(url)
                        result.success("setUrl  success")
                    } else {
                        result.error("-1", "url cannot be null", null)
                    }
                }
                else -> {
                    result.notImplemented()
                }
            }
        }
    }
    

    上述代码我们通过实现PlatformView接口来创建了FImageViewController,当初始化FImageViewController时接受了一个图片URL参数,然后会通过给URL来加载图片
    PlatformView接口主要有两个方法:

    • View getView() : 用于返回Native View,这个View会被嵌入到Flutter的视图结构中
    • void dispose() : 当Flutter要决定销毁PlatformView时会调用这个方法,通常在Flutter的一个widget销毁时调用,我们可以在这个方法中做一些资源释放的工作

    准备PlatformViewFactory

    class FImageViewFactory(private val messenger: BinaryMessenger) :
        PlatformViewFactory(StandardMessageCodec.INSTANCE) {
    
        override fun create(context: Context, viewId: Int, args: Any?): PlatformView {
            return FImageViewController(context, messenger, viewId, args)
        }
    }
    

    上述代码我们通过继承了PlatformViewFactory来创建了FImageViewFactory,PlatformViewFactory包含一个抽象方法

     public abstract PlatformView create(Context context, int viewId, Object args);
    

    该方法用来创建一个Native View,这个View会被嵌入到Flutter的视图结构中

    注册Native UI


    要想将Native UI 提供给Flutter使用,我们需要像Flutter注册Native UI,而注册Native UI的关键是要获取到注册器 (PluginRegistry.Registrar):

    • 继承自FlutterActivity的场景下获取PluginRegistry.Registrar:
      1.在Flutter v1.12以及以前版本中的FlutterActivity中通过 registrarFor("xxx")就可以获取
      2.在Flutter v1.17以及以后的版本中只有 GeneratedPluginRegistrant 才能获取到,而 GeneratedPluginRegistrant 又是Flutter根据插件自动生成的,所以在这种场景下我们需要将 Native封装成插件然后发布到pub仓库并通过pub依赖来让Flutter自动完成注册配置

    • 自己创建 FlutterEngine 的场景,然后通过FlutterEngine来创建 ShiPluginRegistry,通过ShimpluginRegistry来获取PluginRegistry.Registrar,见下面的代码
      当获取到 PluginRegistry.Registrar 之后我们就可以来准备 PlatformViewPlugin了

    准备 PlatformViewPlugin

    object FImageViewPlugin {
    
        fun registerWith(flutterEngine: FlutterEngine){
            val shimPluginRegistry = ShimPluginRegistry(flutterEngine)
            registerWith(shimPluginRegistry.registrarFor("Flutter"))
        }
        
        fun registerWith(register:PluginRegistry.Registrar){
            val fImageViewFactory = FImageViewFactory(register.messenger())
            register.platformViewRegistry().registerViewFactory("FImageView",fImageViewFactory)
        }
    }
    

    通过上述代码我们向Flutter注册了名为 FImageView 的Native View
    上述代码需要在创建Flutter引擎的时候去注册(重要)

    注册flutter / platform_views

    当你的 FlutterEngine是自己手动创建的那么在默认情况下是无法在Flutter侧调用到所注册的Native UI的,在这种情况下使用Native UI 会抛出异常:

    MissingPluginException(No implementation found method create on channel flutter/platform_views)
    

    要想搞明白该问题,需要从Flutter的PlatformView的实现原理说起:Flutter之所以能够识别出来Native UI是因为有PlatforViewsChannel的存在,PlatforViewsChannel是一个借助 MethodChannel 来实现Flutter和Native间通信的插件,该插件的名字就叫: flutter/platform_views,主要用于PlatformView的通信.所以说要想使用Native UI 我们需要有PlatforViewsChannel,而每一个 PlatforViewsChannel 都与之对应的 PlatforViewsController,
    PlatforViewsController 负责 PlatforViewsChannel的创建以及销毁等操作,查看PlatforViewsController的源码我们不难发现它有这样的一个方法:

      public void attach(
          Context context, TextureRegistry textureRegistry, @NonNull DartExecutor dartExecutor) {
        if (this.context != null) {
          throw new AssertionError(
              "A PlatformViewsController can only be attached to a single output target.\n"
                  + "attach was called while the PlatformViewsController was already attached.");
        }
        this.context = context;
        this.textureRegistry = textureRegistry;
        platformViewsChannel = new PlatformViewsChannel(dartExecutor);
        platformViewsChannel.setPlatformViewsHandler(channelHandler);
      }
    

    在该方法中,PlatforViewsController创建了 PlatforViewsChannel
    原理分析这么多,我们不难从中李处导致该问题的主要原因是,在这种自创建 FlutterEngine的情形下 PlatforViewsController 的 attach没有被调用,从而导致 PlatforViewsChannel 没有被创建.
    那么为了解决该文件,我们不妨手动来调用 PlatforViewsController 的 attach方法:

     flutterEngine?.platformViewsController?.attach(activity,flutterEngine.renderer,flutterEngine.dartExecutor)
    

    Dart侧对应Widget封装

    为了方便在Flutter中使用Native UI,我们可以在Dart侧来对 Native UI 做一次封装

    import 'package:flutter/material.dart';
    import 'package:flutter/services.dart';
    import 'package:flutter_module/native/f_image_view_controller.dart';
    
    
    class FImageView extends StatefulWidget {
      final String url;
      const FImageView({Key key, this.url}) : super(key: key);
    
      @override
      _FImageViewState createState() => _FImageViewState();
    }
    
    class _FImageViewState extends State<FImageView> {
      static const StandardMessageCodec _codec = StandardMessageCodec();
    
      @override
      Widget build(BuildContext context) {
        return AndroidView(
          viewType: "FImageView",
          creationParams: {"url": widget.url},
          creationParamsCodec: _codec,
        );
      }
    }
    

    在上述代码中我们通过 AndroidView然后指定他的 viewType来调用了我们在Android侧封装的名 FImageView 的 NativeUi


    相关文章

      网友评论

          本文标题:Flutter中嵌入Android控件(View)

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