Native component embedded solution in flutter

Time:2020-7-15

Absrtact: in the long period of hybrid engineering from native to flutter, it is a good choice to use the more perfect controls in native in flutter in order to smooth the transition. This paper hopes to introduce the use of androidview and the solution of dual end embedded native components based on Android view.

introduction

In the long period of hybrid engineering from native to fluent, it is a good choice to use the more perfect controls in native to smooth the transition. This paper hopes to introduce the use of androidview and the solution of dual end embedded native components based on Android view.

1. Using the tutorial

1.1. DemoRun

The scene of embedding maps may exist in many apps, but the current map SDK does not provide flutter library, and it is obviously not realistic to develop a set of maps by ourselves. In this scenario, the mixed stack is a better choice. We can directly embed a map in native’s drawing tree, but the view embedded in this scheme is not in the drawing tree of flutter. It is a violent and not elegant way, and it is also very hard to use.

At this time, using the official control androidview provided by flutter is a more elegant solution. Here is a simple demo embedded in the map of Gaud. Let’s follow this application scenario to see how Android view is used and how to implement it.

1.2. How to use androidview

The use of androidview is similar to that of methodchannel, which is relatively simple and can be divided into three steps:

Step 1: use androidview in the corresponding position of dart code. You need to pass in anviewTypeThis string will be used to uniquely identify the widget and to establish association with the view of native.

Step 2: add code on the native side and write a platformviewfactory. The main task of platformviewfactory is tocreate()Method to create a view and pass it to flutter (this statement is not accurate, but we can understand it for a moment, and we will explain it later)

Step 3: useregisterViewFactory()Method to register the platformviewfactory that has just been written. This method needs to pass in two parameters. The first parameter needs to be the same as the one previously written on the flutter sideviewTypeCorrespondingly, the second parameter is the newly written platformviewfactory.

The part of configuring the Gaud map is omitted here. The official has more detailed documents, which can be consulted on the Gaode developer platform.

The above are all the operations of using androidview. Generally speaking, it is relatively simple. However, there are two problems that can not be ignored to use Android view

  1. Who decides the final display size of view?
  2. How are touch events handled?

Now let the small idle fish to you one by one answer.

This is my IOS development exchange group: 519832104, no matter you are Xiaobai or Daniel, welcome to settle in. You can share experience, discuss technology, learn and grow together!
Also attached is a copy of the interview questions collected by friends. You need IOS development learning materials and real interview questions. You can enter the group and download them by yourself!

2. Principle explanation

In order to solve the above two problems, we must first understand the essence of the so-called “transmitting view”?

2.1. What is the essence of the so-called “spread view”?

To solve this problem, it is inevitable to read the source code. From a deeper perspective, we can sort out a flow chart like this:

As we can see, what flutter finally gets is a textureid returned by the native layer. According to the knowledge of native, KY h this textureid is the ID corresponding to the drawing data of the view that has been rendered on the native side. Through this ID, you can directly find the corresponding drawing data in the GPU and use it. How does flutter use this ID?

In the previous in-depth understanding of the development of the fluent interface, we also introduced the drawing process of the fluent. I’d like to give you a simple arrangement here

Finally, the framework layer of fluent will submit a layertree to the engine layer. In the pipeline, each leaf node will traverse every leaf node of layertree. Each leaf node will eventually call skia engine to complete the drawing of interface elements. After traversing, call glpresentrenderbuffer (IOS) or glswapbuffer (Android) to complete the screen operation.

There are many types of layers, and the texturelayer is used by androidview. Texturelayer has been introduced in more detail in the previous flutter external texture, so I will not repeat it here. When the texturelayer is traversed to, it will call a method of the engine layerSceneBuilder::addTexture()Pass in textureid as a parameter. Finally, when drawing, skia will directly find the corresponding drawing data in GPU according to textureid, and draw it on the screen.

So who can get this ID can do this operation? The answer is no, of course. Texture data is stored in the thread corresponding to the eglcontext that created it. Therefore, if you operate in other threads, you cannot get the corresponding data. Several concepts need to be introduced here

  • Display object: provides information about the pixel density and size of the display
  • Presentation: it provides android with the ability to draw on the corresponding context and display object, which is usually used for dual screen display.

Instead of introducing presentation, we only need to understand that the external texture is realized by presentation. When creating a presentation, the context corresponding to flutterview and a virtual display object created are passed in, so that the texture data created by native can be directly found by the ID.

2.2. Who decides the final display size of view?

Through the above process, we should all think that the display size seems to be determined by two parts: the size of Android view and the size of Android side view. So who actually decides? Let’s do an experiment?

Create a new flutter project directly, and change the middle into an androidview.

//Flutter
class _MyHomePageState extends State {
  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),
      ),
    );
  }
}

In order to better see the cutting effect, ImageView is used here.

//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() {

        }
    };
}

First of all, let’s look at androidview. The corresponding renderobject of androidview is renderandoidview. There are two possibilities to determine the final size of a renderoject: one is specified by the parent node; the other is to determine the size according to its own situation within the range specified by the parent node. Open the corresponding source code, you can see that there is a very important attributesizedByParent = trueIn other words, the size of androidview is determined by its parent node. We can control the size of androidview by using container, sizedbox and other controls.

The drawing data of androidview is provided by the native layer. What happens when the actual pixel size of the rendered view in native is larger than the size of androidview? In this case, there are two ways to deal with non cutting. Flutter maintains its consistent practice, and all out of the borders widgets are displayed in the way of cutting. The situation described above is regarded as an out of the bounds.

When the actual pixel size of the view is smaller than that of the androidview, it will be found that the view will not be correspondingly smaller (the background color of the container is not exposed), and the places without content will be filled with white. The reason is that in singleviewpresentation:: oncreate, a FrameLayout is used as the rootview.

2.3. How to transfer touch events

Android event flow should be familiar to all of you, top-down transmission, bottom-up processing or backflow. Flutter also uses this rule, but androidview handles gestures through two classes:

Motionevents dispatcher: it is responsible for encapsulating events into native events and passing them to native;

Androidviewgesturerecognizer: responsible for recognizing the corresponding gesture, which has two properties:

cachedEventsandforwardedPointersOnly when the pointer property of pointerevent is in forwardedpoints, it will be distributed. Otherwise, it will be stored in cacheevents. The implementation here is mainly to solve some event conflicts, such as sliding events, which can be handled through gestureregisters. Here, you can refer to the official comments.

/// 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: [],
///   ),
/// )
/// 
/// 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: [ new VerticalDragGestureRecognizer() ],
///     ),
///   ),
/// )

So to sum up, this part of the process is also very simple: the initial stage of events from native to fluent is not within the scope of this article. Flutter handles events according to its own rules. If androidview wins the event, the event will be encapsulated into the corresponding native side event and returned to native through the method channel, and native will handle it according to its own rules Event rules to handle.

3. Summary

3.1. Programme limitations

Xiang Dali said: this solution is produced by Google in order to solve the contradiction between the growing business needs of developers and the backward ecological environment. This contradiction is the main contradiction that a new ecology must face. In order to solve this problem, the simplest way is to allow developers to use the mature controls in the old ecology. Of course, this can temporarily solve the problem of incomplete ecological development of flutter. However, it is inevitable to write dual end code (even if there is no corresponding control for IOS now, and it will be updated later), so it can’t really cross end.

To Xiaoli: there are performance defects in this scheme. In the third comment of androidview class, the official has already mentioned that this is a relatively expensive scheme, so we should avoid using it when using the fluent control. If you have read the article “external texture of flutter” before, you should know that in the scheme of implementing external texture with flutter, the process cost of data from GPU to CPU to GPU is relatively high, and it will cause obvious performance defects in a large number of scenarios. We bypassed the middle CPU step by some means, and implemented this technology in app for processing image resources.

3.2. Practical application

At present, the migration of idle fish from native to flutter has encountered the problem that native local image resources cannot be accessed on the flutter side. Under the condition that both flutter and native will coexist for a long time, it is certainly possible to copy a resource and store it according to the rules of flutter, but it inevitably increases the packet size and is not easy to manage.

Facing this problem, our solution is to learn from androidview’s idea of using texture and optimize it. The image resource normalization of native and fluent is realized. In addition to loading local images located in the native resource directory, you can also use native’s image library to load network images.

Click here to communicate with IOS Daniel immediately

Recommended Today

Analysis of super comprehensive MySQL statement locking (Part 1)

A series of articles: Analysis of super comprehensive MySQL statement locking (Part 1) Analysis of super comprehensive MySQL statement locking (Part 2) Analysis of super comprehensive MySQL statement locking (Part 2) Preparation in advance Build a system to store heroes of the Three KingdomsheroTable: CREATE TABLE hero ( number INT, name VARCHAR(100), country varchar(100), PRIMARY […]