In the previous introduction, we know that almost all objects in Flutter are a widget. Different from "controls" in native development, the concept of widget in Flutter is broader. It can represent not only UI elements, but also some functional components such as: for gesture detection GestureDetector, for APP theme data transfer Theme Wait , and controls in native development usually just refer to UI elements. In the following section, we may use concepts such as "controls" and "components" when describing UI elements. We just need to know that they are widgets, just different expressions in different scenarios. Since Flutter is mainly used to build user interfaces, most of the time, readers can think of a widget as a control and don't have to worry about the concept.
In Flutter, the UI is built and practiced by nesting Widgets in Widgets, so remember that everything in Flutter is a Widget.
In Flutter, the function of a widget is to "describe the configuration information of a UI element", which means that the Widget does not actually represent the display elements that are finally drawn on the device screen. The so-called configuration information is the parameters received by the Widget, such as for Text Generally speaking, the content, alignment, and text style of the text are all its configuration information. Let's take a look at the declaration of the Widget class first:
@immutable final Key? key; @protected @override @override @override @override static bool canUpdate(Widget oldWidget, Widget newWidget) { |
@immutable
It means that the Widget is immutable, which restricts the properties defined in the Widget (that is, configuration information) to be immutable (final). Why are the properties defined in the Widget not allowed to change? This is because, in Flutter, if the properties change, the Widget tree will be rebuilt, that is, a new Widget instance will be recreated to replace the old Widget instance, so it is meaningless to allow the properties of the Widget to change, because once the properties of the Widget itself change itself will be replaced. This is why properties defined in Widget must be final.
widget
The class inherits from DiagnosticableTree
, DiagnosticableTree
the "diagnostic tree", whose main role is to provide debugging information.
Key
: This key
attribute is similar to that in React/Vue. Its main function is to decide whether to reuse the old widget key
next time . The decision condition is in the build canUpdate()
method.
createElement()
: As mentioned above, "one widget can correspond to multiple Element
"; when the Flutter framework builds the UI tree, it will first call this method to generate Element
objects corresponding to the nodes. This method is implicitly called by the Flutter framework, and it is basically not called during our development.
debugFillProperties(...)
The method of overriding the parent class is mainly to set some characteristics of the diagnostic tree.
canUpdate(...)
It is a static method, which is mainly used to build
reuse the old widget when the widget tree is re-created. In fact, it should be: whether to use the new widget object to update Element
the configuration of the corresponding object on the old UI tree; through its source code We can see that as long as newWidget
the oldWidget
sum of runtime Type
and key
is equal at the same time, it will be new widget
used to update
the configuration of the Element
object, otherwise a new one will be created Element
.
The details of Key and widget reuse will be discussed in depth later in this tutorial in the advanced section. We just only need to know that adding keys explicitly to widgets may (but not necessarily) make the UI more efficient when rebuilding. We can ignore this parameter for now, and it will be explained in detail later in this tutorial.
In addition Widget
, the class itself is an abstract class, the core of interface which is to define the createElement()
. In the development of Flutter, we generally do not directly inherit the Widget
class to implement a new component. On the contrary, we usually implement it by inheriting StatelessWidget
or StatefulWidget
indirectly inheriting the widget
class. StatelessWidget
and StatefulWidget
are directly inherited from Widget
classes, and these two classes are also two very important abstract classes in Flutter. They introduce two widget models. Next, we will focus on these two classes
Since Widget only describes the configuration information of a UI element, who does the actual layout and drawing? The processing flow of the Flutter framework is as follows:
Element
class .RenderObject
class.Layer
class .The real layout and rendering logic is in the Render tree, and Element is the glue between Widget and RenderObject, which can be understood as an intermediate proxy. Let's use an example to illustrate, assuming the following Widget tree
|
Note that if the background color is set for the Container, a new ColoredBox will be created inside the Container to fill the background. The relevant logic is as follows:
|
Image internally renders images through Raw Image, and Text internally renders text through RichText, so the final Widget tree, Element tree, and rendering tree structures are shown in the below
StatelessWidget
and StatefulWidget
have no corresponding RenderObject.
StatelessWidget
which is relatively simple. It inherits from the widget
class and overrides the createElement()
method
|
Stateless Element
Indirectly inherited from the Element
class. StatelessWidget
Used in scenarios that do not need to maintain state, it usually build
builds the UI by nesting other widgets in the method, and recursively builds its nested widgets during the construction process. Let's look at a simple example
|
The above code implements a Read
widget that print strings
By convention, the constructor parameters of widgets should use named parameters, and required
keywords must be added to the parameters that must be passed in the named parameters, which is beneficial for static code analyzers to check; when inheriting widgets, the first parameter should usually be Key
. In addition, if the widget needs to receive sub-widgets, then the child
or children
parameter should usually be placed at the end of the parameter list. Also by convention, widget properties should be declared as much as possible to final
prevent accidental changes.
|
build
The method has a context
parameter, which is an instance of the BuildContext
class, which represents the context of the current widget in the widget tree, and each widget corresponds to a context object (because each widget is a node on the widget tree). In fact, it context
is a handle for performing "related operations" in the position of the current widget in the widget tree. For example, it provides methods for traversing the widget tree upwards from the current widget and finding parent widgets according to the widget type. Here's an example of getting a parent widget in a subtree:
|
Note : For BuildContext
we can understand it first. As the content of this book expands, some methods of Context will also be used. Readers can have an intuitive understanding of it through specific scenarios. For BuildContext
more content, we will also introduce it in depth in the advanced part later
Flutter Stateful widget
Same as StatelessWidget
, StatefulWidget
also inherits from the widget
class and rewrites the createElement()
method, the difference is that the returned Element
objects are not the same; in addition StatefulWidget
, a new interface is added to the class createState()
.
Let's look at the StatefulWidget
class definition below
|
StatefulElement
Indirectly inherited from the Element
class. StatefulElement
may be called multiple times createState()
to create a State object.
createState()
It is used to create the state related to StatefulWidget, which may be called multiple times during the life cycle of StatefulWidget. For example, when a StatefulWidget is inserted into multiple positions of the widget tree at the same time, the Flutter framework will call this method to generate an independent State instance for each position. In fact, it is essentially a Stateful Element
corresponding State instance
In StatefulWidget, State objects have a one-to-one correspondence with each other, so in Flutter's SDK documentation, we can often see descriptions such as "remove a State object from the tree" or "insert a State object into the tree". The tree at the time refers to the Element tree generated from the widget tree. The "tree" is often mentioned in Flutter's SDK documentation, and we can judge which tree it refers to based on the context. In fact, no matter which tree it is, the ultimate goal is to describe the structure of the UI and draw information, so when encountering the concept of "tree" in Flutter, unless otherwise specified, we can understand it as "a tree that constitutes the user The node tree of the interface", readers don't need to be entangled in these concepts, or the sentence "get the spirit, forget the shape"
Flutter States
A Stateful Widget class corresponds to a State class. State represents the state to be maintained by the corresponding Stateful Widget. The saved state information in State can be:
setState()
method can be manually called to notify the Flutter framework that the state has changed. After the Flutter framework receives the message, it will re-call its build
method to rebuild the widget tree, so as to update the UI. Purpose.There are two commonly used properties in State:
widget
, which represents the widget instance associated with this State instance, which is dynamically set by the Flutter framework. Note that this association is not permanent, because in the application life cycle, the widget instance of a node on the UI tree may change when it is rebuilt, but the State instance will only be created when it is inserted into the tree for the first time , when rebuilding, if the widget is modified, the Flutter framework will dynamically set State widget to a new widget instance.
context
The BuildContext corresponding to Stateful Widget is the same as the BuildContext of Stateless Widget.