This article was first published on wechat public number — interesting things in the world, handling reprint please indicate the source, otherwise will be held responsible for copyright. Exchange QQ group: 859640274

We haven’t seen each other for a long time, and we haven’t sent an article for more than a month, so today WE send an article to brush the sense of existence. Flutter is very popular recently. I have been looking for materials to learn Flutter this month. After a period of exploration, I found that a lot of information is very “water”. Dart entry, Flutter entry, Flutter data collection, nothing interesting at all. I do not want to write a repetitive and boring article, so this article will briefly discuss some problems and solutions encountered in the learning and development of Flutter.

Reading Instructions:

  • 1. WE – > WsElement, ECWS – > ElementContainerWidgetState, EAL – > ElementActionListener

This article is divided into the following chapters, which can be read on demand:

  • 1. Questions about Flutter — Explain my thoughts and learning experience about Flutter in the form of QA.
  • 2. Transplant a Flutter control — transplant tik Tok sticker controls into a Flutter.
  • 3.Flutter Exploration — Talk about how Flutter works.
  • 4. The tail

The question of Flutter

Are things difficult or easy in the world? For it, then the sufferers are easy already!

Q: How to learn Flutter?

A: It’s A platitude. Opening any of the Flutter articles will pave your way for the next few weeks. But what about a few weeks later? Few articles seem to go on. ** After all, the brain likes simple things best (and I’m no exception), and the difficulty of a task is inversely proportional to its popularity. ** So how to learn Flutter? The so-called: take on its, get among them. I have one word: learn Flutter with the goal of making it your best skill.

Q: Can you give me some information about Flutter?

A: Let me list some information I used to learn Flutter:

  • 1.Dart official website, after reading the official documentation, you are ready for Dart.

  • This open source book contains many examples of Flutter. Tap on all of them to get you started. In particular, the final analysis of Flutter principle can be taken a closer look.

  • 3. The Github repository for Flutter. There are very few articles on the web analyzing the principles of Flutter, so to become a Flutter expert, you must be a trailblazing reader of the Flutter source code at all levels.

Q: Will Flutter kill Native?

A: Flutter is A subset of Native. Before mobile phones were revolutionized, Native engineers were only required to master Flutter in complex companies. There will be no engineers who abandon Native and only build Flutter, because Flutter says that 10,000 is just a UI framework. After all, its own complexity can hardly support a more complex business. The above is just my opinion. If there is any disagreement, please discuss it in the comments section.

Q: What does Flutter do better than Native?

A: Here are some of the advantages of Flutter over Native:

  • 1. Ios and Android, but also the Web, MAC and PC.
  • 2. The Dart language is very modern, much better than Java or OC.
  • 3. Emerging frameworks have no historical baggage.
  • 4. Thermo technology is very attractive.
  • 5. Getting started is easy.

Transplant a FluTter control

Readers who often read my articles should have seen my last article: Tiktok, INSTAGRAM and wechat function Comparison — Story sticker text. In this article, the functions of different stories’ sticker text are compared in detail, and then a sticker framework is implemented on Android. In this chapter I’m going to port this sticker frame to Flutter, believing that it will be more reductive than you think. Next, it is recommended to read the article with the source code. Note that much of this chapter is similar to the section in the previous article on implementing controls on the Android side.

Making the address

Usage: sticker_framework: ^0.0.1

1. Architecture

In our first section, we’ll talk about the architectural implementation of the text sticker control, based on figure 1 below and the code on Github. I suggest you clone the code down, of course, don’t forget to give a star.

Let’s start with figure 1 to describe the overall control architecture

  • 1. 1. Smart refrigerator
    • 1. We need to select a StatefulWidget as the base container. So ElementContainerWidgetState in this picture is a such a State of the container structure, simple summarize it has these features:
      • 1. Handle various gesture events, including one finger and two fingers.
      • 2. Add and remove child widgets. The child widgets here are used to draw various elements.
      • 3. Provide some API for external manipulation of elements.
      • 4. Provide a listener for external users to listen to internal processes.
    • 2. With the draw container, we need to add widgets to the draw container. The Widget needs to have all sorts of data while the user is working on it, so I used it hereWETo encapsulate the Widget that needs to be displayed, which has the following inside:
      • 1. Data required by user operations, such as Scale, rotate, X, and Y.
      • 2. There are ways to update widgets with data.
      • 3. Provide some API for ECWS to update data in WE.
    • 3. ECWS and WE continue to inherit a wide variety of extension controls.
  • 2. Having covered the whole picture, we can talk about the process in detail
    • 1. Start with the horizontal arrow:External/internal calls, the external need to call ECWS to add, delete, change, check and other operations on WE will enter this path, this path can have the following operations:
      • 1. AddElement: Adds an element to the ECWS.
      • 2. DeleteElement: Removes an element from the ECWS.
      • 3. Update: Let us build a Widget based on the current number.
      • 4. FindElementByPosition: Find the topmost WE in the passed coordinates.
      • 5. SelectElement: Select a “WE” and move it to the top layer.
      • 6. UnSelectElement: Unselect a WE.
    • 2. Now for the upright arrow:Gesture event flowThere’s some internal logic going on here, and as we’ll see later, the final flow of events triggers the following sequence of actions:
      • 1. The whole process of single movement: When WE select a WE, WE can move it. Movement here can be divided into beginning, ongoing and ending. Each event calls the corresponding method of WE to update its internal data.
      • 2. The whole process of two-finger rotation and scaling: When WE select a WE, WE can scale and rotate it with our two fingers. It can be divided into beginning, ongoing and ending. The corresponding method of WE is also called here to update the data.
      • 3. Select the element and click again: When WE select a “WE”, WE can click it again.
      • 4. Click blank area: When WE do not click any WE, WE can perform some operations, such as clearing the current WE selected state. This behavior is inheritable and can be overridden by subclasses.
      • 5. Subclass events: We feel that there are fewer events triggered. So when down, move, up to priority calls three methods downSelectTapOtherAction, scrollSelectTapOtherAction, upSelectTapOtherAction. These three methods can be overridden by subclasses and return true to indicate that the event has been consumed and ECWS will not fire any more events. This way subclasses can also extend gestures, such as holding down a place to zoom with one finger, and so on.
      • 7. In my diagram, ECWS also implements a subclass DECWS, which simply adds two gestures:
        • 1. One-finger zooming: Similar to tiktok, you can drag to zoom and rotate elements while holding down the lower right corner of the element.
        • 2. Delete: Like Tiktok, click on the upper left corner of the element to delete the element directly.
    • 3. One feature in Figure 1 that is not actually drawn because it is too much to draw is that almost all the behavior of ECWS in 1 and 2 can be listened on externally. ElementActionListener is the interface responsible for listening. ECWS has a EAL set so listeners can be added to multiple sets.

2. Implementation of technical points

I in the development of the whole control of the time encountered more technical implementation of the difficulties, so this section on the selection of some to tell, so that readers will not be particularly confused when looking at the source code.

(1). Define data structure and draw coordinate system

-- -- -- -- -- code block1----- ws_element.dart

int mZIndex = - 1; // Image hierarchy

  double mMoveX = 0.0; // How far to move the center of the ElementContainerWidget after initialization

  double mMoveY = 0.0; // How far to move the center of the ElementContainerWidget after initialization

  double mOriginWidth; // The width of the content at initialization

  double mOriginHeight; // The height of the content at initialization

  Rect mEditRect; // Drawable area

  double mRotate = 0.0; // The Angle of the image clockwise, based on π

  double mScale = 1.0; // The size of the image zoom

  double mAlpha = 1.0; // Image transparency

  bool mIsSelected = false; // Check whether the command is selected

  bool mIsSingeFingerMove = false; // Whether to move with one finger

  bool mIsDoubleFingerScaleAndRotate = false; // Whether it is in the state of two-finger rotation and scaling

  Widget mElementShowingWidget; // Display content widget

  Offset mOffset; // ElementContainerWidget displacement relative to the screen
Copy the code

Function unmoved data first, data structure is a framework very core things, define a good data structure can save a lot of unnecessary code. So in this section we define the data structure and Widget drawing coordinate system based on code block 1

  • 1. WE use the ECWS WE are in as the drawable region of the View in WE. MEditRect in code block 1 is the rectangle represented by this region. So mEditRect is usually **[0, 0, ECWS. GetWidth, ECWS. GetHeight], and mEditRect is px**.

  • 2. The origin of the coordinate system we define is at the center point of mEditRect, which is also the center point of ECWS. MMoveX and mMoveY represent the distance between view and the origin of the coordinate system respectively. Since they both default to 0, views are generally added to the ECWS in the center of the ECWS by default. The units of these two parameters are px.

  • 3. Our coordinate system has z axis, mZIndex is the coordinate of z axis, Z axis represents the cascading relationship of view, mZIndex 0 means view is at the top of ECWS. MZindex defaults to -1, indicating that the View is not added to the ECWS. MZIndex is an integer.

  • 4. We define mRotate as the clockwise view and the range of mRotate as [-360,360].

    5. We define mScale 1 when the view is not scaled, mScale 2 when the view is enlarged by 2 times, and so on.

  • 6. MOriginWidth and mOriginHeight are the initial size of the view, in units of PX.

  • 7. MAlpha is the transparency of the view. The default value is 1 and less than or equal to 1.

  • 8. The rest of the parameters need not be explained, the code is commented.

(2). How do WE refresh elements

-- -- -- -- -- code block2----- ws_element.dart
    
  add() {
    mElementShowingWidget = initWidget();
  }

  Widget initWidget();

  Widget buildTransform() {
    Matrix4 matrix4 = Matrix4.translationValues(mMoveX, mMoveY, 0);
    matrix4.rotateZ(mRotate);
    matrix4.scale(mScale, mScale, 1);
    return Transform(
      alignment: Alignment.center,
      transform: matrix4,
      child: Opacity(
        opacity: mAlpha,
        child: mElementShowingWidget,
      ),
    );
  }
Copy the code
  • 1. The core code for the refresh element is block 2:
    • 1. When WE first add a WE to ECWS, WE subclasses can initialize their element content by implementing initWidget()
    • 2. Then each time the data is updated, we build a Widget for external use with buildTransform().
    • 3. Inside buildTransfrom, move rotation scaling is implemented via Matrix4 and Transform, while Alpha transformation is implemented via Opacity.

(3). How does ECWS build the entire container

-- -- -- -- -- code block2----- element_container_widget.dart

@override
  Widget build(BuildContext context) {
    RawGestureDetector gestureDetectorTwo = GestureDetector(
      child: GestureDetector(
        child: Stack(
            alignment: AlignmentDirectional.center,
            key: globalKey,
            children: mElementList.map((e) {
              return e.buildTransform();
            })
                .toList()
                .reversed
                .toList()
        ),
        onPanUpdate: onMove,
        behavior: HitTestBehavior.opaque,
      ),
    ).build(context);
    gestureDetectorTwo.gestures[RotateScaleGestureRecognizer] =
        GestureRecognizerFactoryWithHandlers<RotateScaleGestureRecognizer>(
              () => RotateScaleGestureRecognizer(debugOwner: this), (RotateScaleGestureRecognizer instance) { instance .. onStart = onDoubleFingerScaleAndRotateStart .. onUpdate = onDoubleFingerScaleAndRotateProcess .. onEnd = onDoubleFingerScaleAndRotateEnd; });return Listener(
      child: ConstrainedBox(
        constraints: BoxConstraints(
          minHeight: double.infinity,
          minWidth: double.infinity,
        ),
        child: gestureDetectorTwo,
      ),
      behavior: HitTestBehavior.opaque,
      onPointerDown: onDown,
      onPointerUp: onUp,
    );
  }

Copy the code
  • 1. We all know that State needs to return a Widget to the StatefulWidget in build().
  • 2. To hold multiple stacked elements, we use a Stack as a container for elements.
  • 3.Stack packages GestureDetector to handle move events.
  • 4. GestureDetector external packing my custom RotateScaleGestureRecognizer to deal with double rotating zoom event.
  • 5. The Listener is used to listen for finger down and up events.
  • 6. The reasons for this design will be explained later when WE go into Flutter.

3. Source code process analysis

In this section, I will mainly analyze the source code flow of the test demo in the project, so that readers can have a simple understanding of the overall operation of the control. This section focuses on the source code, so be sure to clone the source code and follow along.

(1). Add elements

  • 1. I won’t go over the simple initialization action, but we’ll start with the Add button on main.dart. When I click on it, I’m going to create a StickerElement which is the element THAT I’m testing, but it’s pretty simple and I’m not going to talk about it.
  • 2.addSelectAndUpdateElementIs a composite method that is calledaddElement,selectElement,update, that is, add elements, select elements, update elements. Let’s analyze one by one…
    • 1.addElementThis method does the following:
      • 1. Check the data. If the added WE is empty or the WE is already in ECWS, the addition fails.
      • 2. In ECWS, I maintain a List of WE, in which all WE are stored. Every time WE are added, WE will be added to the top of the List, and the mZIndex of other Weis will be updated accordingly.
      • 3. Call the we. add method, which initializes mElementShowingView with initWidget. As mentioned earlier, the logic of initWidget is defined by subclasses.
      • 4. Call the corresponding method of the listener, and call the method that is automatically unselected (ECWS can be externally determined to be automatically unselected).
    • 2.selectElementAfter WE are added, WE will select it directly, and the code mainly does the following:
      • 1. Check the data. If the WE to be selected is not added to ECWS, the selection fails.
      • 2. Remove the selected WE from the list and add it to the top of the list, then update the mZIndex of the other WE’s.
      • Select * from WE; select * from WE;
      • 4. Call the listener method.
    • 3. Update: With that done, WE need to adjust the WE to its proper state, which I’m sure you’ve all guessed, by calling setState, which triggers the build method WE talked about in Section 2, and then calling each WE buildTransform to return the Widget with updated data.

(2). Element single finger gesture

Element gestures do not need to be invoked externally like adding elements. They are triggered by event distribution. We will not talk about the event distribution mechanism of Flutter here, but only our logic based on it.

  • 1. The processing of element single finger gesture mainly depends on three touch events: Down, move and up. So let’s go straight to the three callback methods set up in ecws-build.
    • 1.onDownThe logic is as follows:
      • 1. Find the WE at the top of the current position according to the down position through findElementByPosition.
      • 2. Call downSelectTapOtherAction if there is currently a selected WE that is the same as the current touch WE. This function can be overridden by subclasses and returns false by default. That is, a subclass can handle the current event first, and if it handles the event, return. If the subclass does not, mark mMode as SELECTED_CLICK_OR_MOVE, indicating that the final gesture may be a click element or a move element. Specific behaviors need to be determined when moving or up.
      • 3. If there is currently a selected WE but it is not the same as the current touched WE, there are two cases: one is that the touched WE does not exist, in this case, mark mMode as SINGLE_TAP_BLANK_SCREEN, and click the blank area of ECWS. Another case is the presence of a touching WE, in which case a WE has been re-selected.
      • 4. If there is no “WE” currently selected, there will be two situations: one is that the “WE” of the touch also does not exist, which means that the blank area is clicked as before. Otherwise, you pick a WE.
    • 2.onMoveChina will give priority to move eventsscrollSelectTapOtherActionThis method can also be overridden by subclasses, which also return false by default. If a subclass handles the event, it returns. Otherwise whenmModeSELECTED_CLICK_OR_MOVE(SELECT WE to MOVE), SELECT(not SELECT WE to MOVE), MOVE(WE to MOVE)In one of the three situations, the movement gesture can be triggered. The specific logic is insingleFingerMoveIn:
      • 1. Invoke singleFingerMoveStart or singleFingerMoveProcess based on the mMode status. SingleFingerMoveStart calls the listener and WE corresponding methods, and there is little logic in it. The listener and WE corresponding methods are also called in singleFingerMoveProcess, but the corresponding methods of WE update the data of mMoveX and mMoveY.
      • 2. Call Update to update the view in WE. Set mMode to MOVE to indicate that the mMode is moving.
    • 3.onUpMethods:
      • 1. MMode is SELECTED_CLICK_OR_MOVE, which can only be confirmed when it is here. The user’s behavior is click after selecting the element.
      • 2. MMode is SINGLE_TAP_BLANK_SCREEN, which means to click on the ECWS blank. OnClickBlank can also be overridden by subclasses to implement some of their own logic.
      • 3. If mMode is MOVE, the call ends only when the MOVE is completed.

Study of Flutter

In this chapter I will look at Flutter from an Android engineer’s point of view and discuss some of the issues I encountered when porting the controls.

1. Compare Flutter to Android

Let’s take a look at how Flutter actually compares to an App written by Android

  • 1. I spent about 10 hours porting the code from Android to Flutter. It took more than 100 hours to design and develop the entire control on Android. So the cost of porting the entire library is not too high.
  • 2. Look at the GIF comparison above and you can see no difference in fluency. I tried it out with a few friends, and none of them found any difference.
  • 3. Figure 3 and Figure 4 show the performance of Flutter and Android respectively. And what we found was that it was true in a lot of reviews. Flutter consumes more memory than Native. I added dozens of elements to the comparison. Finally both ends stabilize to a memory value. The Flutter is around 256MB and the Android is around 128MB.

  • 4. In migrating the code, I summarized the following differences between writing Java and Dart:
    • Dart has a lot of syntactic sugar, and the code is much more streamlined than Java.
    • 2.Dart parameter passing makes writing a Flutter control more like writing a property configuration table.

2. The principle of Flutter

Look at the Flutter through the eyes of an Android engineer

(1) a brief summary of Flutter events

  • 1.LIstener is the basis of gesture: GestureDetector is developed based on LIstener.

  • 2. Events are bottom-up and cannot be truncated

    • 1. Define: bottom-up means from child view to parent view. Top down means from parent view to child view.
    • 2. Those of you who have worked on Android know that events ** in Android are a top-down and bottom-up process. ** in either of the middle loops we can intercept and stop the event from passing.
    • 3. The Flutter event model is bottom-up, and there is currently no action that can interrupt this process.
    • 4. That is, if we use a Listener to listen on any Widget, we prevent the Listener from getting the event during event delivery.
    • 5. The untruncated nature of events is most useful in development: if we use tapUp, tapDown, and other gestures to listen for the lifting and lowering of fingers, these gestures may be washed out by other gestures. At this point we can use the Listener to listen for specific Down and Up events because this is not truncated.
  • 3. During development, we used GestureDetector to encapsulate widgets. The individual gesture call-back we define causes the GestureDetector to generate multiple Gesturerecognizers attached to the current Widget to handle the events received by the Widget.

  • 4. Each finger’s Down, Move, and Up events are a stream of events. When a Down event establishes a Widget chain from the bottom up, GestureRecognizer attached to each Widget in the chain will compete to belong to this stream of events.

  • 5. There is only one GestureRecognizer for a given event flow, and the entire event flow belongs to this GestureRecognizer.

  • 6.GestureRecognizer’s winning mechanism is the flexibility of Flutter to add the feature that the event is not truncated.Gesture can be truncated on a Widget.

  • 7. What is the winning mechanism of Gesture?

    • 1. If there is only one GestureRecognizer in a contest, he wins.
    • 2. If there are multiple Gesturerecognizers in a race, the lowest GestureRecognizer wins.
    • 3. If a competition has a different GestureRecognizer:
      • GestureRecognizer defines a timeout mechanism. Some Gesturerecognizers define a time threshold for an event and if no other GestureRecognizer applies to extend the threshold, this GestureRecognizer wins. For example: TapGestureRecognizer defines the down event and after 100 ms, if no other GestureRecognizer extends the threshold, then it gets the event stream itself.
      • 2. The definition and LongPressGestureRecognizer time threshold is 500 ms, if there is no other GestureRecognizer applied for an extension after 500 ms threshold is to get a flow of events.
      • 3. Then TapGestureRecognizer and LongPressGestureRecognizer are, by the length of the down event to determine who wins.

(2). The drawing logic of Flutter

Core principles of Flutter

Four, tail

Ah! The article feels a bit anticlimactic, spanning several weeks from start to finish. Working overtime and moving house in between drained my blood. I would have done a little more research on Flutter, but it feels like writing will take longer, so I’ll leave it at that. Next I will write a series of articles analyzing the principles of Flutter and the Flutter Sdk. So stay tuned for more! Ps: a pep up, and then exhaust, three and failure. It is a perfect representation of my writing process, I hope readers do not copy me.

Serial articles

  • 1. Write a Douyin app from scratch — start
  • 4. Copied a Douyin App from scratch — log and buried point and preliminary back-end architecture
  • 5. Copied a Douyin App from scratch — App architecture update and network layer customization
  • 6. Write a Douyin App from scratch — start with audio and video
  • 7. Write a Douyin App from scratch — a minimalist video player based on FFmpeg
  • 8. Write a Douyin App from scratch — build a cross-platform video editing SDK project
  • 9. Copied a Douyin App from scratch — fully analyzed the source code of Android drawing mechanism and Surface family

No angst peddling, no clickbait. Share some interesting things about the world. Topics include but are not limited to: science fiction, science, technology, the Internet, programmers, computer programming. The following is my wechat public number: Interesting things in the world, dry goods waiting for you to see.