Flutter接管了应用渲染层,方法通道可以获得原生底层能力,对于需要底层渲染的视图,比如浏览器、相机、地图以及一些原生自定义视图,我们自己在Flutter上再实现一遍,需要花费大量的精力。

 

为了复用原生系统已有的视图,我们可以采用混合视图的方式,我们在Flutter的Widget树中提前预留一块空白区域,在Flutter画板(即FlutterView/FlutterViewController)中嵌入一个与空白区域完全匹配的原生视图。但这种方案,由于嵌入的视图不在Flutter的渲染层级中,需要Flutter和原生宿主App上做大量的适配工作。

 

为了解决在Flutter上复用原生视图的问题,Flutter提供了平台视图(Platform View)。通过平台视图,我们可以将原生控件包装成Flutter控件,从而加入到Flutter的渲染树中,获得与Flutter控件一致的用户体验。

 

方法通道解决的是Flutter与原生系统之间的逻辑通信问题,平台视图解决的是Flutter与原生系统之间的视图复用问题。

 

平台视图

为了解决Flutter层复用原生控件的问题,Flutter提供了平台视图(Platform View)让我们可以通过简单的Dart接口,将Android/iOS平台的原生视图插入到Flutter的渲染树中,实现Flutter视图和原生视图的混用。

Android flutter 原生 页面互调 flutter 原生view_控件

一次完整的平台视图调用的流程(平台视图的使用过程和方法通道使用过程大致相似,也和一次网络请求类似):

  • 首先,Flutter通过向原生视图的Flutter封装类(Android上是AndroidView、iOS上是UIKitView)传入视图标志符,用于发起创建原生视图的请求;
  • 然后,原生系统接收到请求,创建原生视图,交给平台视图工厂类(PlatformViewFactory)实现;
  • 最后,原生系统将视图标识符和平台视图工厂类进行关联,让Flutter发起的原生视图创建请求可以直接找到对应的视图创建工厂。

 

Flutter实现平台视图调用接口

在Flutter中,针对不同的平台使用对应的视图封装类(Android使用AndroidView、iOS使用UIKitView),发送创建对应平台的原生视图,并传入视图的唯一标识符,用来建立和原生视图的关联:

Android flutter 原生 页面互调 flutter 原生view_控件_02

上面的是Flutter端调用原生系统控件的代码,可以看出,Flutter端使用原生系统控件与普通的Flutter控件无明显差异,只是需要根据当前的平台使用AndroidView和UIKitView进行原生控件的调用。

 

Flutter端的代码实现了,剩下的就是Android、iOS端的控件实现了。在Flutter引用的原生宿主入口处,我们需要注册一个监听接口,用来监听来自Flutter的平台视图创建请求,当接收到请求之后,按照viewType(AndroidView/UIKitView中传递的viewType参数),来决定调用哪个视图工厂类,被调用的视图工厂类在其create方法中返回一个原生视图的封装类类,在该原生视图封装类中生成原生视图。Flutter在获得这个原生视图之后,已经是在Flutter层面的控件,就可以像普通控件一样加入到Flutter渲染树中。

 

Android端实现平台视图

首先是视图工厂类SampleViewFactory.java,在视图工厂类的create方法中返回一个原生视图封装类:

Android flutter 原生 页面互调 flutter 原生view_iOS_03

之后是原生视图封装类SimpleViewController.java,在这个类中我们创建并返回对应的原生视图:

Android flutter 原生 页面互调 flutter 原生view_封装类_04

在上面代码的第8行,可以初始化生成你需要的Android原生视图,比如地图控件等。

 

最后,我们需要绑定监听来自Flutter端的调用平台视图的请求,和方法通道一样,我们仍然是在原生宿主的Flutter入口处(MainActivity.java)进行注册:

Android flutter 原生 页面互调 flutter 原生view_Android_05

这样Android端就可以监听到来自Flutter端的平台视图调用请求,并通过视图工厂/视图封装类生成原生视图,并将其返回给Flutter端,以Flutter控件的方式加入到Flutter层的渲染树进行渲染。

 

iOS端实现平台视图

首先,是平台视图封装类SampleViewController.m:

Android flutter 原生 页面互调 flutter 原生view_Flutter_06

然后是平台视图工厂类SampleViewFactory.m:

Android flutter 原生 页面互调 flutter 原生view_控件_07

最后需要在原生宿主的Flutter入口处进行接口注册(AppDelegate.m):

Android flutter 原生 页面互调 flutter 原生view_Flutter_08

【注意】

在iOS平台上,Flutter内嵌UIKitView还处于技术预览的状态,因此我们若要在iOS平台上使用平台视图,还需要在Info.plist文件中开启:

<dic>
    ...
    <key>io.flutter.embedded_views_preview</key>
    <true/>
</dic>

之后我们就可以在Flutter中使用平台视图了:

Scaffold(
    backgroundColor: Colors.yellow,
    body: Container(
        width: 200,
        height: 200,
        child: SampleView(),
    ),
);

代码实现效果:

Android flutter 原生 页面互调 flutter 原生view_Flutter_09

上面的代码实现了在Flutter端,通过平台视图显示原生系统的视图控件,如何实现Flutter和平台视图的交互呢,这就要使用方法通道。

 

我们知道Android/iOS的视图控件状态的改变是命令式的,所以我们可以在原生视图封装类中将修改视图实例的接口,以方法通道的方式暴露给Flutter,从而让Flutter可以修改原生视图的状态。

 

下面的例子中在Flutter端创建了一个原生视图控制器,通过方法通道,实现与平台视图的交互:

Android flutter 原生 页面互调 flutter 原生view_封装类_10

然后声明Flutter端的StatefulWidget控件:

Android flutter 原生 页面互调 flutter 原生view_Android_11

 

Android端的代码实现:

Android flutter 原生 页面互调 flutter 原生view_Flutter_12

 

iOS端代码的实现:

Android flutter 原生 页面互调 flutter 原生view_封装类_13

之后我们就可以在Flutter中实现平台视图的通信了:

class DefaultState extends State<DefaultPage> {
    NativeViewController _controller;

    @override
    void initState() {
        // 初始化原生 View 控制器
        _controller = NativeViewController();
        super.initState();
    }

    @override
    Widget build(BuildContext context) {
      return Scaffold(
           // ...
           // 内嵌原生 View
           body:  Container(
               width: 200, 
               height:200,
               child: SampleView(controller: _controller)
           ),
           // 设置点击行为:改变视图颜色 
           floatingActionButton: FloatingActionButton(onPressed: () {
                _controller.changeBackgroundColor();
           }),
      );
    }
}