Flutter Widgets

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.

Widget interface

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 
abstract class Widget extends DiagnosticableTree {
  const Widget({ this.key });

  final Key? key;

  @protected
  @factory
  Element createElement();

  @override
  String toStringShort() {
    final String type = objectRuntimeType(this, 'Widget');
    return key == null ? type : '$type-$key';
  }

  @override
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.defaultDiagnosticsTreeStyle = DiagnosticsTreeStyle.dense;
  }

  @override
  @nonVirtual
  bool operator ==(Object other) => super == other;

  @override
  @nonVirtual
  int get hashCode => super.hashCode;

  static bool canUpdate(Widget oldWidget, Widget newWidget) {
    return oldWidget.runtimeType == newWidget.runtimeType
        && oldWidget.key == newWidget.key;
  }
  ...
}

 

  • @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 DiagnosticableTreeDiagnosticableTree the "diagnostic tree", whose main role is to provide debugging information.

  • Key: This keyattribute 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

 

Widgets Tree In Flutter

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:

  1. Generate an Element tree based on the Widget tree, and the nodes in the Element tree inherit from the Element class .
  2. Generate a Render tree (render tree) based on the Element tree, and the nodes in the render tree inherit from the RenderObject class.
  3. Generate a Layer tree based on the rendering tree, and then display on the screen, the nodes in the Layer tree inherit from the 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

Container( 
  color: Colors.blue, 
  child: Row( 
    children: [
      Image.network('https://www.example.com/1.png'), 
      const Text('Widget'),
    ],
  ),
);

 

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:

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

 

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

 

Flutter Widget Tree

 

  1. Among the three trees, there is a one-to-one correspondence between Widget and Element, but not one-to-one correspondence with RenderObject. For example StatelessWidget and StatefulWidget have no corresponding RenderObject.
  2. The rendering tree will generate a Layer tree before it is displayed on the screen. We will introduce this later in the principle chapter. In the previous chapters, we just only need to remember the above three trees

 

Flutter Stateless Widget

StatelessWidget which is relatively simple. It inherits from the widget class and overrides the createElement()method

@override
StatelessElement createElement() => StatelessElement(this);

 

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

class Read extends StatelessWidget  {
  const Read({
    Key? key,  
    required this.text,
    this.backgroundColor = Colors.grey, 
  }):super(key:key);
    
  final String text;
  final Color backgroundColor;

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Container(
        color: backgroundColor,
        child: Text(text),
      ),
    );
  }
}

 

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.

Widget build(BuildContext context) {
  return Read(text: "hello flutter");
}

 

Context

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:

class ContextRoute extends StatelessWidget  {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Flutter Context"),
      ),
      body: Container(
        child: Builder(builder: (context) {
         
          Scaffold scaffold = context.findAncestorWidgetOfExactType<Scaffold>();
          Text("Flutter Context")
          return (scaffold.appBar as AppBar).title;
        }),
      ),
    );
  }
}

 

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

abstract class StatefulWidget extends Widget {
  const StatefulWidget({ Key key }) : super(key: key);
    
  @override
  StatefulElement createElement() => StatefulElement(this);
    
  @protected
  State createState();
}

 

  • 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:

  1. Can be read synchronously when the widget is built.
  2. It can be changed during the widget life cycle. When the State is changed, its 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:

  1. 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.

  2. context The BuildContext corresponding to Stateful Widget is the same as the BuildContext of Stateless Widget.

 

 


Advertisements