Frederik Schweiger Link: The Layer Cake
Flutter is an excellent cross-platform development framework. It allows us to develop beautiful apps quickly and with very little code. Meanwhile, the hot overload mechanism greatly improves our development efficiency, and apps developed on Flutter run smoothly. Can achieve 120 FPS. So, have you ever wondered how the Flutter can be so fast? What’s the secret? Or, more directly, how exactly does Flutter work? Hopefully, the following will give you some answers.
You’ve probably heard that a Flutter is a Widget. Your app is a Widget, your text is a Widget, and the padding surrounding a Widget is also a Widget. Even gesture recognition is implemented through widgets. Of course, this is not the whole truth, if I told you that it is true that widgets make our development easier and more efficient, but can we create a Flutter app without even one widget? Let me explore this framework a little more.
First, framework introduction
You may have in some similar to the ‘introduction to Flutter introduction article to Flutter have roughly understanding, but you may not be ready to go to understand the concept behind the hierarchical structure and principle, perhaps you also like me, but not dull to look at this picture anything, don’t worry, I’ll help you to understand, Let’s take a look at this picture first.
The Flutter framework consists of a number of abstract layers. At the top of these layers are the frequently used Material and Cupertino widgets. Below this layer are the widgets that encapsulate more general components. The scaffolds and FlaotingActionButton come from the Meterial package. The scaffolds and FlaotingActionButton come from the Meterial package. Column and GestureDector come from the Widgets layer.
Below the Widget layer is the Rendering layer. Rendering layer simplifies the layout and Rendering process. It’s dart: abstraction of the UI layer. Dart: The UI is the lowest level of the framework and handles the communication with the Flutter Engine.
In short, the higher the level, the more encapsulation, the easier it is to use, and then the lower the level, the more freedom, the finer the control granularity, and of course, the more complex it is to use.
1, dart: UI layer
Dart: UI Library exposes the lowest level of services on which the Flutter framework builds applications, such as input drivers, drawing text, layout and rendering systems, etc.
So you can build a Flutter App just by using classes in the DART: UI library (such as Canvas, Paint, and TextField). But if you’re familiar with drawing directly on a canvas, you know that drawing is not just about drawing a stick figure image. Drawing management is a pain in the first place, but also how to manage and organize the layout, and when you touch elements on your app, you have to be able to calculate and respond to hit-testing.
So what does that mean? This means that you have to accurately calculate the coordinates of all the elements in your layout, and then implement the ability to draw to the screen and respond to external input events (touch screens) that can be captured. For each frame drawing on the screen, you have to realize these functions, in this way to develop the APP, if only to display a text on a blue block this simple application interface, may also can achieve, but if it is a shopping application or a game, this is unlikely to be impossible, Not to mention handling animations and implementing sophisticated interface effects yourself. I’m just telling you from my experience that it’s a nightmare for us developers to develop this way.
2, Rendering layer
Flutter Rendering layer, which is the Flutter Rendering tree. The Widgets layer uses RenderObject for layout and drawing. Normally, if you are using a custom RenderBox to implement special effects, this layer provides classes, but most of the time, our interaction with this layer is when we encounter a layout problem and debug it.
The Rendering layer is the first abstraction layer on the DART: UI layer and does all the complex mathematical work for us (such as constantly tracking coordinate values of computed elements), while the Rendering layer is the only Rendering layer that does the work with RenderObjects. You can think of RenderObjects as the engine of a car. It is through RenderObjects that our app can actually be displayed on the screen. The rendering tree composed of RenderObjects will be layered and rendered onto the screen by Flutter. To optimize these complex processes, Flutter also uses intelligent algorithms that cache the result of each calculation, updating only a small part of it each time.
Usually, Flutter uses RenderBox instead of RenderObject. This is because Flutter developers have found that a simple layout model with box constraints can do a good job of building beautiful interfaces. Imagine that every widget has its own box constraint model. In a box, it calculates its constraints, size, etc., and then adds the widget to a calculated box system. In this system, if a widget changes, the system simply recalculates the constraints of the box in which the widget resides.
3, Widgets layer
The Flutter Widgets framework layer
The Widgets layer is an interesting one. It provides us with UI components that we can use directly, all of which can be divided into three categories, each with a corresponding RenderObject to handle.
- Layout related
Column and Row, for example, are widgets that make it easy to arrange components horizontally or vertically.
- Draw correlation
Components like Text and Image help us display or render something onto the screen.
- Gesture detection is related to the GestureDetector component, which can detect on-screen clicks or swipes.
Typically, we build our own components or tree of components by using these basic components. For example, we can implement a button component by wrapping a Container around a GestureDetector to listen for click events.
Material & Cupertino
The Widgets in this layer are the encapsulation of the Widgets layer, but the Widgets that implement the Material and Cupetino styles are provided for us to use.
Generally speaking, the Idea behind the Flutter framework is abstraction and encapsulation, which makes it easier for us developers to develop. This layer is the fourth layer of the Flutter framework. The components in this layer are packaged by the Flutter framework and provided for our use. Material is the Material design style and Cupetino is the iOS style. AlertDialog, Switch, and FloatingActionButton. If you’re an iOS developer, So you might want to use CupertinoAlertDialog, CupertinoButton and CupertinoSwitch, which you might be more familiar with.
Flutter created this layer of Widgets in the Material and Cupertino styles to reduce the burden on developers.
2. Comprehensive analysis
How does RenderObject relate to Widgets? How does Flutter create a layout? What is Element?
Now that I’ve given you a brief introduction to framework architecture, let’s take a look at real practice, such as the simple control tree below.
Ps: The widgets we use in real development, such as Text, are made up of many other widgets, replaced by abstract widgets (SimpleContainer and SimpleText) for demonstration purposes.
The APP we’ve built is very simple. It consists of three Stateless widgets: SimpleApp, SimpleContainer, and SimpleText. What happens when we call the runApp() method of Flutter?
When a Flutter runs runApp(), the following list of things will happen behind it:
1. Flutter will build a Widgets tree that contains these three Widgets.
2. Flutter traverses the Widget tree, creates its associated Element objects using the createElement method inside the Widget, and then builds these objects into the Element tree.
3. Finally, Element calls the createRenderObject() method to create the RenderObject object that it wants to associate with and build a third tree, the RenderObject tree.
When a Flutter has created three corresponding trees, it can be described as follows:
The Flutter creates three different trees, one Widget tree, one Element tree and one RenderObject tree. Each Element holds a reference to the Widget and RenderObject.
Let’s look at what a RenderObject is.
RenderObject implements all the logic for rendering widgets, and instantiation of RenderObject objects is a very important operation, as well as layout, rendering, and gesture detection. Therefore, instances of RenderObject objects should be cached in memory as much as possible, and then reclaimed until they are not used.
Next comes Element, which acts as the glue between RenderObject and Widget. Widtet is immutable, and RenderObejct is mutable. An Element can be thought of as an instance of a Widget at a specific location in the Widget tree, and it also holds references to its associated Widget and RenderObject.
Why use three trees instead of one? In short, it’s for performance. When the Widget tree changes, Flutter uses the Element tree to compare the new Widget tree with the RenderObject tree that was created. When a Widget is of the same type as the previous Widget, the Flutter does not recreate the RenderObject (too costly), just update its changed parameter configuration. Since the creation and destruction of widgets in Flutter are lightweight, it makes sense to use widgets as configuration parameters to describe the current state of the app. RenderObject creation is a heavyweight operation, so RenderObejct should be reused as much as possible.
However, in the Flutter framework, elements are removed, so we don’t have to deal with them very often. The context passed in the Build (BuildContext Context) method of each Widget is the Element that implements the BuildContext interface, which is why individual widgets of the same category differ.
Third, in-depth analysis
Because widgets are immutable, the entire Widget tree needs to be rebuilt whenever a Widget’s configuration changes. For example, when we change the color of the Container in our code above to red, the Flutter framework triggers the reconstruction of the Widget tree. Then, with the help of Element, Flutter compares the first Widget in the new Widget tree with the first RenderObject in the RenderObject tree. The second Widget in the Widget tree is compared to the second RenderObject in the RenderObject tree, and so on, until the Widget tree is compared to the RendObject tree.
Flutter follows a basic principle: Determine whether the newly created Widget is of the same type as the old one. If they are not of the same type, remove the Widget, Element, and RenderObject from their trees (including their subtrees) and create new objects. If it’s a type, just change the configuration in the RenderObject and then iterate over the other objects.
In our example, the SimpleApp Widget is of the same type and configured as the old SimpleApp apprender, so nothing will happen. The next item in the Widget tree is the SimpleContainer Widget, which has the same type as before, but its color has changed, and the RenderObject configuration has changed. Because SimContainer still needs a SimpleContainerRender to render, Flutter simply updates SimpleContainerRender’s color properties and then asks it to re-render. The other objects remain the same.
When recreated, the widget tree looks like this (note that Element and RenderObject are still the same instance object).
This process is very fast because Flutter is very good at creating lightweight Widgets. Those heavyweight RenderObjects remain unchanged until their corresponding widgets are removed from the Widget tree. What happens if the Widget’s type changes?
For example, replace SimpleText with SimpleButton
Just like above, Flutter will rebuild the Widget tree, traverse the tree, and compare the Widget type to the object in the RenderObject.
At this point, the state of the three trees is shown below, and the Element and SimpleTextRender corresponding to the Widget that changed the type have been removed
Because SimpleButton has a different type from the corresponding Element in the Element tree, Flutter will remove the corresponding Element and the corresponding SimpleTextRender from the other two trees. Flutter will then rebuild the Element and RenderObject instances corresponding to SimpleButton.
The final state is as follows
The new RenderObject tree has then been rebuilt, rearranged and painted on the screen. In this, Flutter does a lot of optimization and uses a caching strategy that allows us to manage these objects without having to manage them manually.
Four,
Now you should have some idea of how Flutter works, why it can render complex layouts and run smoothly. There is no mention of State. Flutter introduces State to improve the overall performance of the process. Hopefully this article has helped you understand the Flutter framework.
Recommended reading
Widgets, RenderObjects, and Elements