This is the second day of my participation in the August More text Challenge. For details, see: August More Text Challenge

This article is translated from the rendering section of the Overall architecture overview of the Flutter Architectural Overview on the Website for a better understanding of state management and rendering mechanisms in subsequent chapters.

preface

As a cross-platform framework, The rendering mechanism of Flutter is very different from that of many mixed development frameworks. Current frameworks such as React-Native, UniApp, and Weex actually create a layer of abstract mapping on top of the Native UI in an attempt to smooth out the differences between different platforms. Most implementations are based on translation interactions between Javascript and the native, with actual rendering still relying on the native platform. The advantage of this approach is that it retains the features of the native UI, but there is a major drawback, which is the difference between platforms — the same set of code runs on different platforms with different interface and design effects.

Unlike the above frameworks, the actual rendering process of Flutter does not rely on the native, but instead uses the Skia rendering engine written in C/C++ to render the interface. Dart code for the draw interface is compiled into native code, but Skia is used for rendering. The Skia rendering engine is built into the Flutter to keep rendering performance up to date even if the user’s phone has not been updated to the latest version of the mobile operating system.

From interaction to GPU

Taking a user input as an example, the rendering process of the entire interaction to the CPU is shown in the following figure, where the framed part is the rendering process.

Build links

The following simple code snippet builds a simple hierarchical component tree.

Container(
  color: Colors.blue,
  child: Row(
    chindren: [
      Image.network('https://www.juejin.com/1.png'),
      const Text('A'),,),);Copy the code

When Flutter needs to render the component tree above, it calls the build method. The build method returns a tree of components to draw the UI based on the state of the current app. During this process, the build method can also determine whether new components need to be introduced based on the state. For example, in the example code above, Container has a color and child attribute. As you can see from the Container source code, if its color property is not empty, a ColoredBox is inserted to represent its color.

if(color ! =null) current = ColoredBox(color: color! , child: current);Copy the code

Accordingly, the Image and Text components may also insert child components, such as RawImage and RichText, during the build process. The resulting component tree is deeper than the code hierarchy, as shown in the figure below. This is why, when using debugging tools such as the Flutter Inspector, you find that the component tree is much deeper than the code level.

During the construction phase, the Flutter transforms the components of the code into element trees, with each element corresponding to a widget. Each element represents a specific component instance at its corresponding location in the tree. There are two basic types of elements:

  • ComponentElement: The host of other elements.
  • RenderObjectElement: Elements involved in layout and drawing.RenderObjectElementIs the medium for their components, is the actual rendering object (RenderObject).BuildContextProcessing component treewidgetLocation, so it can be passedBuildContextGets a reference to the element. thiscontextIs that we call something likeTheme.of(context)thecontextWill be passed as a parameter tobuildMethods.

This is what the component tree above looks like when transformed into the element tree.

Since components are immutable and there is A parent-child relationship between nodes, any change in the component tree (such as Text(‘A’) to Text(‘B’) in the previous example) returns A new set of components. But this does not mean that the underlying element mapping has to be rebuilt. The element tree is maintained when the display frame is switched, so the element is critical for performance — even if the component tree is completely destroyed allowing the Flutter to continue to function properly through the cached element map. Therefore, by simply checking for changes in the component tree, Flutter can rebuild only those elements of the element tree that need to be reconfigured.

Layout and Rendering

Often, applications don’t have a single component. An important part of the UI framework’s job is to efficiently lay out the tree of components, sizing and positioning each element before it is drawn to the screen. The base class for each node in the render tree is RenderObject, which defines an abstract model for layout and rendering. Each RenderObject is aware of its parent, but little information about its children and layout constraints. This allows an effectively abstract RenderObject to handle a wide variety of scenarios. During the construction phase, Flutter creates or updates objects for each RenderObjectElement in the element tree that inherits from RenderObject. RenderObject is a basic class: RenderParagragh renders text, RenderImage renders images, and RenderTransform does the transformation before drawing its children. The above example only renders RenderObjectElement objects after the render phase.

Most of the Flutter components are rendered using an object inherited from the RenderBox, which represents a 2d Cartesian fixed size RenderObject. RenderBox provides a box constraint model that establishes the minimum and maximum width and height for each component to be rendered. To perform the layout, the Flutter uses a depth-first tranversal, passing the size elements from parent to child. In order to determine its own size, the child element must follow the constraints passed down from the parent node. Within the constraint rules established by the parent, the child element passes its size information to the parent.

After a single walk through the tree, each object is given a size within its parent’s layout constraints, and the paint method can be called.

The box constraint model is very powerful and requires only O(n) time complexity to complete the layout of objects:

  • The parent node can specify the size of the child element by setting the maximum and minimum constraints to the same value. For example, in the case of a mobile App, the top level render object will set the size of its children to the screen size (children can choose how to use the space, for example, you can set the center render within the constraint).
  • The parent node can specify the width of the child element and then give the child element a dynamic height (or vice versa). A practical example is text, which can satisfy the constraints horizontally and then dynamically set the height vertically based on the number of text.

This is still useful when the child node object needs to know how much space is available to decide how to render its own content. Using the LayoutBuilder component, the child node object can examine the constraints passed down and then decide how to use them. Such as:

Widget build(BuildContext context) {   
  return LayoutBuilder(
    builder: (context, constraints) {       
      if (constraints.maxWidth < 600) {         
        return const OneColumnLayout();       
      } else {         
        return constTwoColumnLayout(); }}); }Copy the code

The root node of all renderObjects is the RenderView, which represents the entire output of the render tree. When the platform requests to render a new frame, the compositeFrame method is called, which is part of the Render tree root node RenderView. This creates a SceneBuilder to trigger an update to the screen. When the scene is complete, the RenderView object passes the combined image to the window.render method defined in the DART: UI, and then gives control to the GPU to complete the drawing of the image.

conclusion

This article describes the basic mechanism of rendering the Flutter. Understanding the rendering mechanism will help you understand the mapping between widgets and actual rendering, which will help you better understand how the state management tool performs component updates in subsequent state management. Next we will begin the chapter on state management of Flutter.


I am the island code farmer, wechat public account with the same name. This is a column on The Introduction and practice of Flutter.

👍🏻 : feel a harvest please point to encourage!

🌟 : Collect articles, convenient to read back!

💬 : Comment exchange, mutual progress!