Native embedded in the assembly in a proper posture Flutter

introduction

In the long transition from mixed Native to Flutter project period, in order to smooth the transition, the more perfect the use of Native Flutter controls will be a good choice. This article hopes to introduce solutions use AndroidView and double-ended on this basis to expand embedded Native components

1. Tutorial

1.1. DemoRun

This scenario is likely to embed the map in many App and will continue to exist, but now do not provide Flutter map SDK library, and develop their own set of maps is clearly not realistic. In this scenario, the form of a mixed stack is a good choice. We can be embedded directly in a Map Native drawing tree, but this program is not embedded in the Flutter View drawing tree, is a more elegant way without violence, use is also very strenuous.

At this time, the use of official controls AndroidView Flutter is a more elegant solution to the. Here to do a simple embed high moral map of the demo, let us follow this scenario, look at the use AndroidView and implementation principle.

Native embedded in the assembly in a proper posture Flutter

1.2. AndroidView use

The use and similar AndroidView MethodChannel, relatively simple, is divided into three steps: first step: using AndroidView dart at a corresponding position code, need to pass a viewType use, this will be used to uniquely identify the String Widget, with Native to the View and associates.

Native embedded in the assembly in a proper posture Flutter
Step two: add the native side of the code, write a PlatformViewFactory, PlatformViewFactory main task is to create a View in the create () method and pass it on to Flutter (this is not accurate, but let's be so understanding, follow-up will be explained)
Native embedded in the assembly in a proper posture Flutter

The third step: using registerViewFactory () method to register just written PlatformViewFactory, this method requires two arguments, the first parameter and the prior need to write viewType Flutter end corresponding to the second parameter of the just written PlatformViewFactory.
Native embedded in the assembly in a proper posture Flutter

配置高德地图的部分这里就省略不说了,官方有比较详细的文档,可以去高德开发者平台进行查阅。
以上便是使用AndroidView的所有操作,总体看起来还是比较简单的,但是真正要用起来,还是有两个无法忽视的问题:
1.View最终的显示尺寸由谁决定?2.触摸事件是如何处理的?
下面就让在下来给各位一一解答。

2. 原理讲解

想要解决上面的两个问题,首先必须得理解所谓"传View"的本质是什么?

2.1. 所谓"传View"的本质是什么?

要解决这个问题,自然避免不了的需要去阅读源码,从更深的层面去看这个传递的整个过程,可以整理出一张这样的流程图:
Native embedded in the assembly in a proper posture Flutter

我们可以看到,Flutter最终拿到的是native层返回的一个textureId。根据native的知识ky h这个textureId是已经在native侧渲染好了的view的绘图数据对应的ID,通过这个ID可以直接在GPU中找到相应的绘图数据并使用,那么Flutter是如何去利用这个ID的呢?我这里也给大家再简单整理一下

Native embedded in the assembly in a proper posture Flutter

Flutter的Framework层最后会递交给Engine层一个layerTree,在管线中会遍历layertree的每一个叶子节点,每一个叶子节点最终会调用Skia引擎完成界面元素的绘制,在遍历完成后,在调用glPresentRenderBuffer(IOS)或者glSwapBuffer(Android)按完成上屏操作。

Layer的种类有很多,而AndroidView则使用的是其中的TextureLayer。TextureLayer在被遍历到时,会调用一个engine层的方法SceneBuilder::addTexture() 将textureId作为参数传入。最终在绘制的时候,skia会直接在GPU中根据textureId找到相应的绘制数据,并将其绘制到屏幕上。

那么是不是谁拿到这个ID都可以进行这样的操作呢?答案当然是否定的,Texture数据存储在创建它的EGLContext对应的线程中,所以如果在别的线程进行操作是无法获取到对应的数据的。这里需要引入几个概念:

  • 显示屏对象(Display):提供合理的显示器的像素密度和大小的信息
  • Presentation:它给Android提供了在对应的上下文(Context)和显示屏对象(Display)上绘制的能力,通常用于双屏异显。

这里不展开讲解Presentation,我们只需要明白Flutter是通过Presentation实现了外接纹理,在创建Presentation时,传入FlutterView对应的Context和创建出来的一个虚拟显示屏对象,使得Flutter可以直接通过ID找到并使用Native创建出来的纹理数据。

2.2. View最终的显示尺寸由谁决定?

通过上面的流程大家应该都能想到,显示尺寸看起来像是由两部分决定的:AndroidView的大小,Android端View的大小。那么实际上到底是有谁来决定的呢,让我们来做一个实验?

直接新建一个Flutter工程,并把中间改成一个AndroidView。

//Flutter
class _MyHomePageState extends State<MyHomePage> {
  double size = 200.0;

  void _changeSize() {
    setState(() {
      size = 100.0;
    });
  }

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text(widget.title),
      ),
      body: Container(
        color: Color(0xff0000ff),
        child: SizedBox(
          width: size,
          height: size,
          child: AndroidView(
            viewType: 'testView',
          ),
        ),
      ),
      floatingActionButton: new FloatingActionButton(
        onPressed: _changeSize,
        child: new Icon(Icons.add),
      ),
    );
  }
}

在Android端也要加上对应的代码,为了更好地看出裁切效果,这里使用ImageView。

//Android
@Override
public PlatformView create(final Context context, int i, Object o) {
    final ImageView imageView = new ImageView(context);
    imageView.setLayoutParams(new ViewGroup.LayoutParams(500,500));
    imageView.setBackground(context.getResources().getDrawable(R.drawable.idle_fish));
    return new PlatformView() {
        @Override
        public View getView() {
            return imageView;
        }

        @Override
        public void dispose() {

        }
    };
}

Native embedded in the assembly in a proper posture Flutter
首先先看AndroidView,AndroidView对应的RenderObject是RenderAndroidView,而一个RenderObject的最终大小的确定是存在两种可能,一种是由父节点所指定,还有一种是在父节点指定的范围中根据自身情况确定大小。打开对应的源码,可以看到其中有个很重要的属性sizedByParent = true,也就是说AndroidView的大小是由其父节点所决定的,我们可以使用Container、SizedBox等控件控制AndroidView的大小。
AndroidView的绘图数据是Native层所提供的,那么当Native中渲染的View的实际像素大小大于AndroidView的大小时,会发生什么呢?通常情况下,这种情况的处理思路无非就两种选择,一种是裁切,另一种是缩放。Flutter保持了其一贯的做法,所有out of the bounds的Widget统一使用裁切的方式进行展示,上面所描述的情况就被当作是一种out of the bounds。
当这个View的实际像素大小小于AndroidView的时候,会发现View并不会相应地变小(Container的背景色并没有显露出来),没有内容的地方会被白色填充。这其中的原因是SingleViewPresentation::onCreate中,会使用一个FrameLayout作为rootView。

2.3. 触摸事件如何传递

Android的事件流大家应该都很熟悉了,自顶向下传递,自底向上处理或回流。Flutter同样是使用这一规则,但是其中AndroidView通过两个类来去处理手势:
MotionEventsDispatcher:负责将事件封装成Native的事件并向Native传递;
AndroidViewGestureRecognizer:负责识别出相应的手势,其中有两个属性:

Native embedded in the assembly in a proper posture Flutter
cachedEvents and forwardedPointers, only when PointerEvent the pointer will go to the property in forwardedPointers distributed, otherwise there cacheEvents in. To achieve here is to solve the conflict of some event, such as a sliding events can be processed by gestureRecognizers, where you can refer to the official notes.

/// For example, with the following setup vertical drags will not be dispatched to the Android view as the vertical drag gesture is claimed by the parent [GestureDetector].
/// 
/// GestureDetector(
///   onVerticalDragStart: (DragStartDetails d) {},
///   child: AndroidView(
///     viewType: 'webview',
///     gestureRecognizers: <OneSequenceGestureRecognizer>[],
///   ),
/// )
/// 
/// To get the [AndroidView] to claim the vertical drag gestures we can pass a vertical drag gesture recognizer in [gestureRecognizers] e.g:
/// 
/// GestureDetector(
///   onVerticalDragStart: (DragStartDetails d) {},
///   child: SizedBox(
///     width: 200.0,
///     height: 100.0,
///     child: AndroidView(
///       viewType: 'webview',
///       gestureRecognizers: <OneSequenceGestureRecognizer>[ new VerticalDragGestureRecognizer() ],
///     ),
///   ),
/// )

So sum up, sum up this part of the process is actually very simple: The event originally from Native to Flutter this stage is beyond the scope of this article, Flutter in accordance with its own rules to handle events, if AndroidView won the event, the event will be Native package to the corresponding end event and return channel by means of Native, Native then according to the rules of their process to handle the event.

Guess you like

Origin blog.51cto.com/14332859/2412067