This is the sixth day of my participation in the First Challenge 2022
An obvious should be to combine these trees into one tree. By already simplifying the trees, simplification should be done by already simplifying them into one tree. Performance, Clarity and Type Safety don’t do this, so let’s take a look at what the three trees are and how they are related.
What exactly is a Widget
Introduction of the Widget
Widgets are the building blocks of a Flutter app’s user interface, and each widget is an immutable declaration of part of the user interface.
Widgets are blocks of UI code that we developers build, spacing using Padding and streaming layout using Wrap. So what exactly is a Widget? Take a look at the Widget’s official description (2.2 SDK) :
Describes the configuration for an [Element].
Widgets are the central class hierarchy in the Flutter framework. A widget
is an immutable description of part of a user interface. Widgets can be
inflated into elements.which manage the underlying render tree.
Widgets themselves have no mutable state (all their fields must be final).
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.
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.
Copy the code
-
A Widget is simply descriptive information about an Element and is a configuration file. You can place Widget information on an Element. There is a second class, Element, which manages rendering.
-
Widgets are at the heart of the whole Flutter system and are responsible for interacting with users.
-
The Widget itself is constant. So its properties are all final. As for the StatefulWidget, its State is maintained in the third object State, not in the Widget, so the Widget itself remains unchanged.
-
Widgets can be placed multiple times in the Widget tree. For example, write the Text component as a member variable and hang it on multiple nodes.
To summarize: In a nutshell, widgets represent our UI, and their relationship to Element is similar to the relationship between an XML file and a View on Android, which is simply a configuration.
Here’s an example:
We developed: Text(” SSS “), which tells Element: Please display a Text for me.
What are the Widget
The principle of Flutter design is to have as few concepts as possible. This concept corresponds to the code of the top class or base class of Flutter. The Widget class is an embodiment of this concept, and its direct subclass is the KIND of UI that developers can use. The diagram below:
StatelessWidget
Stateless widgets are widgets that have no State objects and simply override the build method to display the UI. We use it to display areas that have no business logic, such as Containers, Text, Builder, and so on.
You know that a Widget can be called an Element, and the Element that a StatelessWidget corresponds to is StatelessElement
StatefulWidget
Stateful is a Widget that creates a State object in which the UI can be displayed depending on the data at runtime. The official state management article states that a single-state UI can use a StatefulWidget.
The most common TabBar, AppBar, and so on are statefulWidgets.
You might have said that widgets are immutable.
This type of Widget, which is truly immutable and all of its properties are final, puts the display logic into State.
The build method in State is where we display the UI.
The Element corresponding to a StatefulWidget is a StatefulElement.
⚠️ Note: StatefulElement and StatelessElement are composite widgets that do not directly participate in drawing processes, and are subclasses of ComponentElement. So there is no draw-related information in the code for them or their corresponding widgets.
ProxyWidget
Proxy type widgets that are not used to display UI directly, such as common inheritedWidgets, ParentDataWidget of hook parent data, and so on.
The number of widgets of this type is minimal and is generally used as support for functional implementations, such as MediaQuery nodes, as we know them.
abstract class ProxyWidget extends Widget {
const ProxyWidget({ Key? key, required this.child }) : super(key: key);
final Widget child;
}
Copy the code
What it displays is the Child field.
RenderObjectWidget
Render type Widget that creates a RenderObject for Element that contains layout, rendering, and other processes. Most of the components provided by the system are of the render type. Padding-top, Opacity, Row, etc.
This type of Widget rewrite is complex and requires specifying the rendering process. Let’s look at the general process in terms of common Offstage components.
class Offstage extends SingleChildRenderObjectWidget {
const Offstage({ Key? key, this.offstage = true, Widget? child })
: assert(offstage ! =null),
super(key: key, child: child);
/ /... Omit code
// create Render
@override
RenderOffstage createRenderObject(BuildContext context) => RenderOffstage(offstage: offstage);
// Create Element
@override
_OffstageElement createElement() => _OffstageElement(this);
}
Copy the code
For render type widgets, you specify both Element (_OffstageElement) and RenderObject (RenderOffstage). See, Widget doesn’t retain any state; its methods are called by others.
class RenderOffstage extends RenderProxyBox {
RenderOffstage({
bool offstage = true,
RenderBox? child,
}) : assert(offstage ! =null),
_offstage = offstage,
super(child);
/ /... Code to simplify
@override
void performLayout() {
if(offstage) { child? .layout(constraints); }else {
super.performLayout(); }}@override
void paint(PaintingContext context, Offset offset) {
if (offstage)
return;
super.paint(context, offset); }}Copy the code
As you can see, the layout and layout of its internal nodes are determined by offstage. As long as offstage is not true, it does not draw or layout its children. So, the effect is not to show.
With these widgets, you can display any UI and perform any function. For example, if a developer wants to display the UI, they can first look at the RenderObjectWidget provided by the system. If not, try using the StatelessWidget, StatefulWidget to combine widgets.
If you don’t want to display the UI and want to provide some functionality, you can try rewriting the ProxyWidget. The common Provider we use is InheritWidget. The stream for streaming layout is ParentDataWidget.
How do you express this relationship from a code perspective?
Each Widget has its Element.
So, Flutter is all about widgets. EveryThing is a Widget.
What is an Element? How is the Element and Widget relationship bound?
What is the Element
Introduction of Element
In the process of development, we are rarely directly involved in the development of Element, but when we troubleshoot problems, we often end up in Element, especially when the data and UI are out of sync. and
Let’s start with Element’s official description
An instantiation of a [Widget] at a particular location in the tree. Widgets describe how to configure a subtree but the same widget can be used to configure multiple subtrees simultaneously because widgets are immutable. An [Element] represents the use of a widget to configure a specific location in the tree. Over time, the widget associated with a given element can change, for example, if the parent widget rebuilds and creates a new widget for this location. Elements form a tree. Most elements have a unique child, but some widgets (e.g., subclasses of [RenderObjectElement]) can have multiple children.Copy the code
From the above we can see:
-
Element is the actual content of the Widget and configures its position in the book based on the location of the Widget
-
As the program runs, the Widget that corresponds to an Element changes, such as when the parent Widget is rebuilt, so the child tree changes.
-
Elements also form a tree, with some elements having one or more child nodes
That’s why we have the following:
The Element tree form
So when did the Element tree form? Let’s look at the life cycle first.
There are only a few key methods, because this is going to be an introduction to the relationship between three trees, so we’re not going to go into detail about the updating process.
Starting from the parent’s point of view, the mount method of the child’s Element is the first method in the lifecycle when the parent wants to display the child. Element’s unmount method is called when the parent moves out of the child.
The mount method
Let’s start with the description of the mount method.
/// Add this element to the tree in the given slot of the given parent.
/// The framework calls this function when a newly created element is added to
/// the tree for the first time. Use this method to initialize state that
/// depends on having a parent. State that is independent of the parent can
/// more easily be initialized in the constructor.
///
/// This method transitions the element from the "initial" lifecycle state to
/// the "active" lifecycle state.
/// .
Copy the code
Mount accomplished one thing
- Inserts this Element into the specified position on the Element tree
This method is called only once, changing Element’s state from Initial to Active.
The code is also simple:
@mustCallSuper
void mount(Element? parent, Object? newSlot) {
///Omit code..._parent = parent; _slot = newSlot; _lifecycleState = _ElementLifecycle.active; _depth = _parent ! =null? _parent! .depth +1 : 1;
if(parent ! =null) {
_owner = parent.owner;
}
///Omit code...
}
Copy the code
The code above is the tree formation process:
- Specifies who its parent is
- Specify your position in the parent node – what slot is
- Its depth — depth is +1 deeper than its parent
The two trees are related
We have seen the formation of the Element tree, but how does an Element fit into a Widget? The Element class contains the _widget field, where the value of the _widget field is assigned, and where the two trees are associated.
We see two assignments in the code:
- A constructor
Element(Widget widget)
: assert(widget ! =null),
_widget = widget;
Copy the code
- Update method
void update(covariant Widget newWidget) {
///. Omit code
_widget = newWidget;
}
Copy the code
So we know that when we construct an Element, it is already associated with the Widget. The resulting Element tree must correspond to the Widget tree.
Let’s look at the association process using the StatelessWidget as an example to be more specific.
When StatelessWidget is converted to StatelessElement, the Framework calls the inflateWidget.
Element inflateWidget(Widget newWidget, Object? newSlot) {
// omit code...
final Element newChild = newWidget.createElement();
return newChild;
Copy the code
The newWidget is a StatelessWidget, so it goes to the createElement of the StatelessWidget
@override
StatelessElement createElement() => StatelessElement(this);
Copy the code
StatelessElement is constructed
StatelessElement(StatelessWidget widget) : super(widget);
Copy the code
The constructor finally calls Element’s constructor
Element(Widget widget)
: assert(widget ! =null),
_widget = widget;
Copy the code
This completes the association process.
summary
An Element is a Widget Widget that corresponds to an Element one by one. When constructing an Element, two trees have been associated. The mount method of Element forms the Element tree.
We saw the formation and association of the Element tree above, but we didn’t see any render related actions in Element. So what’s relevant about rendering? So that’s RenderObject.
What is the RenderObject
RenderObject profile
One of the widgets we saw above is a render type Widget called RenderObjectWidget. In contrast to widgets, RenderObjectWidget requires subclasses to create renderObjects. Let’s first look at the official description of the RenderObject.
An object in the render tree.
The [RenderObject] class hierarchy is the core of the
rendering library's reason for being.
[RenderObject]s have a [parent], and have a slot called
[parentData] in which the parent [RenderObject] can store
child-specific data, for example,the child position. The
[RenderObject] class also implements the basic
layout and paint protocols.
Copy the code
Judging from the above description, we can know that:
-
RenderObect is a small node in the rendering tree, and RenderObject is the core class of the rendering system. Similar to widgets and elements, these are concepts. It also has a lot of specialized subclasses.
-
RenderObject has two important properties: parent and parentData. The parent node is the parent node. ParentData is the information stored by the parent node and allocated to its children. What does it mean to assign to children? Such as index information, location information and so on. For example, Row assigns an index number to each child node, you in the first position, you in the second position, and so on.
-
RenderObject implements the most basic layout and drawing rules. However, it does not specify the implementation of subclasses, nor does it specify the coordinate system. For example, how many child nodes does a subclass have, whether the child nodes are in cartesian coordinates, whether the child nodes are out of bounds, and so on.
So we know what the RenderObject is. So what are widgets, Elements, and RenderObjects related to each other?
RenderObject tree associated
First, let’s look at the relationship among the three, as shown below:
-
The top-level Widget is closely related to the Element, not directly to the RenderObject. Only the rendered widgets are closely related.
-
Element acts as a bridge and holds widgets and RenderObjects. Finding Element is the key.
-
Widgets are responsible for creating and updating renderObjects, but not for maintaining them.
Widget provides the create method, so where the create is called is where the tree is formed. RenderObjectElement maintains the RenderObject, so where the value is assigned is where the association is.
From the above Element analysis, we can guess that the mount method is most likely.
@override
void mount(Element? parent, Object? newSlot) {
super.mount(parent, newSlot); / / first place
/ /... Omit code
_renderObject = widget.createRenderObject(this); / / in the second place
attachRenderObject(newSlot); / / the third place
}
Copy the code
The first code’s mount of super completes the Widget’s association with the Element
The assignment of the second code completes the node association
The third attach completes the formation of the tree
_ancestorRenderObjectElement = _findAncestorRenderObjectElement(); _ancestorRenderObjectElement? .insertRenderObjectChild(renderObject, newSlot);Copy the code
You insert child nodes one by one
Here the three trees are completely formed:
Each Widget has an Element, but only render widgets have renderObjects. Trictly speaking, the RenderObject tree is a subset of the Element tree. The RenderObject tree is a subset of the Element tree.
conclusion
In summary, the three tree formation and association are introduced, each tree has its own job. Reusing elements is important for performance. In the next article, we will introduce Reusing elements.