As an emerging cross-platform solution, what does Flutter do with rendering?

Architecture diagram of the Flutter Framework:

The underlying Framework, the Flutter engine, is responsible for drawing graphics (Skia), typesetting (libtxt) and providing the Dart runtime. The engine is all implemented in C++. The Framework layer allows us to invoke the power of the engine in Dart language.

1.Widget

Widgets are the basic unit of control implementation in Flutter, similar in meaning to UIView in Cocoa Touch. Widgets store a view’s configuration information, including layout, properties, and so on. So it’s just a lightweight, ready-to-use data structure. There are no significant performance issues when building as a structure tree, or even recreating and destroying the structure tree. But we typically develop controls that use Meterial or Cupertino, so when you add a Widget to a view, what’s behind this rendering?

Official Widget comment analysis:

/// Describes the configuration for anElement. (Provides configuration information for Element)
Copy the code

The Widget is closely related to Element and provides it with some information.

/// If you wish to associate mutable state with a widget, consider using a
/// [StatefulWidget], which creates a [State] object (via
/// [StatefulWidget.createState]) whenever it is inflated into an element and
/// incorporated into the tree.

/* The Widget is populated with an Element that manages the underlying render tree. The Widget itself has no mutable state (all fields must be final). If you want to put the State variable associated with the Widget, you can use StatefulWidget, StatefulWidget by using StatefulWidget. CreateState method to create the State object, and expand the to Element and incorporated into the tree; * /
Copy the code

Instead of managing State and rendering directly, the ↑ Widget manages State through the State object.

/// A given widget can be included in the tree zero or more times. In particular
/// a given widget can be placed in the tree multiple times. Each time a widget
/// is placed in the tree, it is inflated into an [Element], which means a
/// widget that is incorporated into the tree multiple times will be inflated
/// multiple times.

/* The given Widget can be included in the tree (zero or more times). A given Widget can be placed in the tree multiple times. Each time a Widget is added to the tree, it is expanded to an Element, meaning that widgets that are added to the tree multiple times will be expanded to the corresponding Element multiple times. * /
Copy the code

In actual interface development, a view tree may contain multiple TextwidGets (widgets may be used multiple times), but these widgets, all called TextwidGets, are packed into separate elements.

/// The [key] property controls how one widget replaces another widget in the
/// tree. If the [runtimeType] and [key] properties of the two widgets are
/// [operator==], respectively, then the new widget replaces the old widget by
/// updating the underlying element (i.e., by calling [Element.update] with the
/// new widget). Otherwise, the old element is removed from the tree, the new
/// widget is inflated into an element, and the new element is inserted into the
/// tree.

/* The Key property in Widget controls how one Widget replaces another Widget in the tree. If the runtimeType and key attributes of the two widgets are equal (==), the new Widget replaces the old Widget by updating Element (that is, by calling element.update with the new Widget). Otherwise, if the runtimeType and key attributes of the two widgets are not equal, the old Element will be removed from the tree, and the new Widget will be expanded into a new Element, which will be inserted into the tree. * /
Copy the code

The ↑Widget uses a Key and runtimeType to determine whether it is in the Element Tree to update, insert, or delete.

2.Element

Developers don’t have to manipulate elements manually, most of the time the framework’s internal logic does.

Open the Element class and you’ll find two important Element properties, Widget and renderObject.

  /// The configuration for this element.
  @override
  Widget get widget => _widget;
  Widget _widget;

  /// The render object at (or below) this location in the tree.
  ///
  /// If this object is a [RenderObjectElement], the render object is the one at
  /// this location in the tree. Otherwise, this getter will walk down the tree
  /// until it finds a [RenderObjectElement].
  RenderObject get renderObject {
    RenderObject result;
    void visit(Element element) {
      assert(result == null); // this verifies that there's only one child
      if (element is RenderObjectElement)
        result = element.renderObject;
      else
        element.visitChildren(visit);
    }
    visit(this);
    return result;
  }
Copy the code

↑ means that Element holds both Widgets and renderObjects. The Element’s role is clear. It acts as middleware to separate the control tree from the render object.

So the entire life cycle of Element is defined roughly:

The framework layer creates an Element by calling the widget. createElement method of the Widget that will be used as Element's initial configuration information. The framework layer adds the newly created Element to the tree at the given slot in the given parent level by calling the mount method. The mount method is responsible for extending any Widget to Widget and calling attachRenderObject as needed to attach any associated render objects to the render tree. At this point, Element is considered "active" and may appear on the screen. In some cases, the parent (Element) may change the Widget used to configure this Element, for example because the parent recreates a new state. When this happens, the framework layer calls the update method of the new Widget. The new Widget will always have the same runtimeType and key properties as the old Widget. If the parent Element wants to change the Widget's runtimeType or key at this location in the tree, you can do so by unmounting the Element and expanding the Widget at that location. At some point, an ancestor Element may decide to remove that Element (or an intermediate ancestor Element) from the tree, which the ancestor Element itself does by calling deactivateChild. Disabling the intermediate ancestor removes the Element's render object from the render tree and adds the Element to the list of inactive elements in the Owner property, allowing the framework layer to call the deactivate method on the Element. At this point, the Element is considered an "invalid state" and does not appear on the screen. An Element can remain inactive until the current animation frame ends. At the end of the animation frame, any elements that are still inactive will be unloaded. If an Element is regrouped into the tree (for example, because it or one of its ancestors has a global key reused), the framework layer removes the Element from the inactive Element list in the Owner property and calls the Element's Activate method. And re-append Element's render object to the render tree. (At this point, Element is again considered "active" and may appear on the screen.) If an Element is not reassembled into the tree at the end of the current animation frame, the frame layer calls the Element's unmount method. At this point, the element is considered "disabled" and will not be incorporated into the tree in the future.Copy the code

3.RenderObject

An object in the render tree. Renders an object in the tree. From its name, we can intuitively know that it is responsible for rendering. In fact, it is responsible for all the layout, drawing, and event response, and we may often have to deal with it when developing complex views.

RenderObjectElement, a subclass of Element, also forms a Render Tree, and each RenderObject is saved for reuse when updated. The data built by Render Tree is added to the LayerTree required by Engine. Engine uses the LayerTree to synthesize and raster views and submit them to the GPU.

The Tree formed by 1, 2, and 3 is similar to the iOS model Tree –> Render Tree –> render Tree.

4.Layout

Once the preparation is complete, the layout is needed for concrete display on the screen. The layout can calculate the true size of the space taken up by each node. When building the view tree, the node’s size constraint is from the top to the bottom of the tree, while when calculating the layout, traversing the tree is depth-first, so the order of obtaining the layout data is bottom-up. Just like the Web, the layout of Flutter is based on a box model.

5.Painting

When drawing, each node draws itself first, followed by its children. For example, node 2 is a view with a green background color. After drawing itself, draw child nodes 3 and 4, which may have alpha property, so red node 5 will be synthesized when the layout overlapped after drawing. Therefore, from the direction of data flow, the order of obtaining the final Layer is bottom-up.

Flutter uses boundaries to mark nodes that need to be rearranged and redrawn. When entering and exiting redrawn boundaries, Flutter forces a new layer to be changed so that other nodes do not become contaminated or trigger reconstruction. This boundary is called the Relayout boundary and the Repaint boundary respectively.

Conclusion:

So whenever I use a Widget, the rendering process of the Flutter interface goes through three stages: layout, drawing, and composition. Layout and drawing are done in the Flutter framework, and composition is taken care of by the engine.

References:

Flutter view basic introduction Widget, Element, RenderObject

Flutter principle and practice

Analysis of Flutter rendering pipeline

Because Chinese website

Flutter’s Rendering Pipeline