Flutter Widget source code analysis and actual combat

Flutter Widget source code analysis and actual combat

Widget

The elements displayed on all pages in flutter are composed of widgets. The difference from native android development is that widgets in flutter not only represent UI elements, they can also be completely unrelated to UI such as GestureDetector, which inherits from GestureDetector StatelessWidget

The function of Widget is similar to the style file in native android development, which is used to describe the style of UI. The element that is actually drawn on the screen is finally

@immutable
abstract class Widget extends DiagnosticableTree { 
//DiagnosticableTree  
  
  const Widget({ this.key }); //key 

  // Element 
  @protected
  Element createElement();

  @override
  String toStringShort() {
    return key == null ? '$runtimeType' : '$runtimeType-$key';
  }

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

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

StatelessWidget

Stateless widgets are generally used to draw some static UI (for example: Text) or to provide functions that have nothing to do with the UI (for example: GestureDetector is used to manage gesture-related functions). The source code is as follows:

abstract class StatelessWidget extends Widget {
  
  const StatelessWidget({ Key key }) : super(key: key);
  
  @override
  StatelessElement createElement() => StatelessElement(this);
  
  @protected
  Widget build(BuildContext context);
 
 }
 

StatelessWidget is used in scenarios where there is no need to maintain state. It usually builds UI by nesting other Widgets in the build method. During the construction process, it will recursively build its nested Widgets, as follows

class TestLess extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      child: Text("test"),
    );
  }
}
 

StatefulWidget

Variable state widget

abstract class StatefulWidget extends Widget {

  const StatefulWidget({ Key key }) : super(key: key);

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

  @protected
  State createState();
}
 

The difference from StatelessWidget is that a new interface createState() is added to the StatefulWidget class. A StatefulWidget class corresponds to a State class, and State represents the state to be maintained by its corresponding StatefulWidget. The following are the best practices for StatefulWidget:

  • Try to prevent the widgets that need the state of the table from the child nodes, so that when you change the entire rendering tree, you only need to update one widget. If you prevent it from the parent node, it will cause the widgets of the entire child node of the current node to be Is re-rendered.

  • Try to reduce the nesting level of widgets returned in the build method. Ideally, a StatefulWidget only contains one child widget of type RenderObjectWidget. For example: RichText, but obviously this is impractical, but the closer a widget is to this ideal, the higher the efficiency.

  • If the subtree has not changed, cache the widget representing the subtree and reuse it every time you use it. For widgets to be reused, it is more effective than creating new (but with the same configuration) widgets. Decomposing stateful parts into widgets with sub-parameters is a common way to do this.

  • Use constsmall parts whenever possible . (This is equivalent to caching the widget and reusing it.)

  • Avoid changing the depth of any created subtree or changing the type of any widget in the subtree. For example, instead of returning the children or children contained in [IgnorePointer], you always wrap the child widgets in [IgnorePointer] and control the [IgnorePointer.ignoring] property. This is because changing the depth of the subtree requires rebuilding, laying out and drawing the entire subtree, while changing only the properties will require as few changes as possible to the render tree (for example, in the case of [IgnorePointer], no layout) or redraw necessary).

  • If the depth must be changed for some reason, consider wrapping the public part of the subtree in a widget with [GlobalKey] that remains consistent throughout the life cycle of the stateful widget. (If there are no other widgets to easily assign keys, the [KeyedSubtree] widget may be useful for this.)

Below is a YellowBirdframework called a subclass of stateful widgets. In this example, [State] has no actual state. State is usually expressed as a private member field. In addition, usually widgets have more constructor parameters, and each parameter should be a finaltype.

 class YellowBird extends StatefulWidget {
   const YellowBird({ Key key }) : super(key: key);

   @override
   _YellowBirdState createState() => _YellowBirdState();
 }

 class _YellowBirdState extends State<YellowBird> {
   @override
   Widget build(BuildContext context) {
     return Container(color: const Color(0xFFFFE306));
   }
 }
 

The following example shows a more general widget Bird, it can be given a color and a child widget, and it has some internal state, you can call a method to change it

class Bird extends StatefulWidget {
   const Bird({
     Key key,
     this.color = const Color(0xFFFFE306),
     this.child,
   }) : super(key: key);

   final Color color;
   final Widget child;

   _BirdState createState() => _BirdState();
 }

 class _BirdState extends State<Bird> {
   double _size = 1.0;

   void grow() {
     setState(() { _size += 0.1; });
   }

   @override
   Widget build(BuildContext context) {
     return Container(
       color: widget.color,
       transform: Matrix4.diagonal3Values(_size, _size, 1.0),
       child: widget.child,
     );
   }
 }
 

By convention, the widget constructor only uses named parameters. You can use [@required] to mark named parameters as required. By convention, the first parameter is [key], and the last parameter is child,children

There are two common attributes in State

  • widget

Represents the widget instance associated with the State instance

  • BuildContext

Build the context of the widget

Life cycle

  • initState

It will be called when the Widget is inserted into the Widget tree for the first time, for each State object

The framework will call this method once for each [State] object it creates. Override this method to perform initialization, which depends on where this object is inserted in the tree (ie [context]) or the widget used to configure this object (ie [widget])

If the [build] method of [State] relies on an object that can change its state, such as [ChangeNotifier] or [Stream], or another object that can subscribe to receive notifications, then you must subscribe and enter [initState], [didUpdateWidget ] And [dispose] to cancel the subscription:

You cannot use [BuildContext.inheritFromWidgetOfExactType] in this method. However, [didChangeDependencies] will be called immediately after this method, and [BuildContext.inheritFromWidgetOfExactType] can be used there.

  • didChangeDependencies

Called when the dependency of the State object changes

If the parent Widget is rebuilt and requested to update this position in the tree to display a new Widget with the same [runtimeType] and [Widget.key], the framework will update the [widget] property of this [State] object to reference the new Widget and then use the above Call this method with a Widget as a parameter.

Override this method to respond when [widget] changes (for example, start implicit animation).

After calling [didUpdateWidget], the framework always calls [build], which means that any call to [setState] in [didUpdateWidget] is redundant.

  • build

It is mainly used to build Widget subtree

  • reassemble

This callback is specifically provided for development and debugging, and will be called during hot reload

  • didUpdateWidget

When the widget is rebuilt, the framework will call canUpdate to detect the new and old nodes at the same position in the Widget tree, and then decide whether to update

  • deactivate

This callback is called when the State object is removed from the tree. In some scenarios, the Flutter framework will reinsert the State object into the tree, such as when the subtree containing this State object moves from one position of the tree to another (which can be achieved through GlobalKey). If it is not reinserted into the tree after removal, the dispose() method will be called immediately

  • dispose

Called when the State object is permanently removed from the tree; resources are usually released in this callback.

Layout component related ( original )

Layout components will contain one or more subcomponents, and different layout components have different layout methods for the subcomponents. The Element tree is created through the Widget tree (via Widget.createElement()). The Widget is actually the configuration data of the Element. In Flutter, Widgets are divided into three categories according to whether they need to contain child nodes, corresponding to three kinds of Elements, as shown in the following table:

Widget Element use
LeafRenderObjectWidget LeafRenderObjectElement The leaf nodes of the Widget tree are used for widgets that have no child nodes. Usually, the basic components belong to this category, such as Text and Image.
SingleChildRenderObjectWidget SingleChildRenderObjectElement Contains a sub Widget, such as: ConstrainedBox, DecoratedBox, etc.
MultiChildRenderObjectWidget MultiChildRenderObjectElement Contains multiple child Widgets, generally have a children parameter and accept an array of Widgets. Such as Row, Column, Stack, etc.

StatelessWidget and StatefulWidget are two base classes for combining Widgets, and they are not associated with the final rendering object (RenderObjectWidget).

The final rendering operation is to build a real RenderObjectWidget, such as Text, in the build() method, which actually inherits from StatelessWidget, and then builds its subtree through RichText in the build() method, and RichText inherits from LeafRenderObjectWidget

Actual combat

The specific effects are as follows:

The relevant source code is as follows:

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ListView(
      physics: AlwaysScrollableScrollPhysics(),
      shrinkWrap: true,
      children: <Widget>[
        _swipe(),
        _checkWidget(),
      ],
    );
  }

  // 
  Widget _swipe() {
    return Container(
      height: 180, // 
      child: Swiper( // 
        itemBuilder: (BuildContext context, int index) {
          return InkWell(
            onTap: () {},
            child: Image.network(
              "https://sr.aihuishou.com/cms/image/63689137818430153041559824.png",
              fit: BoxFit.fill,
            ),
          );
        },
        itemCount: 3,
        pagination: SwiperPagination( // 
            alignment: Alignment.bottomRight,
            builder: FractionPaginationBuilder(
                fontSize: 20,
                color: Colors.white,
                activeFontSize: 20,
                activeColor: Colors.white)),
        autoplay: true,
      ),
    );
  }

  // 
  Widget _checkWidget() {
    return Container(
      margin: EdgeInsets.only(left: 10, right: 10),
      child: Column( // 
        children: <Widget>[
          Container(
            margin: EdgeInsets.only(top: 20),  
            child: Row( // 
              crossAxisAlignment: CrossAxisAlignment.center,
              // 
              mainAxisAlignment: MainAxisAlignment.spaceBetween, 
              children: <Widget>[
                Text(
                  " ",
                  style: TextStyle(color: Colors.black, fontSize: 16),
                ),
                Row(
                  children: <Widget>[
                    Text(
                      " ",
                      style: TextStyle(color: Colors.black54, fontSize: 12),
                    ),
                    Icon(
                      Icons.keyboard_arrow_right,
                      color: Colors.grey,
                    )
                  ],
                )
              ],
            ),
          ),
          Container(
            margin: EdgeInsets.only(top: 10, bottom: 10),
            padding: EdgeInsets.fromLTRB(10, 15, 10, 15),
            decoration: BoxDecoration( // 
              color: Colors.white,
              borderRadius: BorderRadius.all(
                Radius.circular(5), // 
              ),
            ),
            child: Row(
              mainAxisAlignment: MainAxisAlignment.start,
              mainAxisSize: MainAxisSize.max,
              crossAxisAlignment: CrossAxisAlignment.center,
              children: <Widget>[
                // 
                Expanded(
                  flex: 1, // 1
                  child: Row(
                    mainAxisAlignment: MainAxisAlignment.start,
                    children: <Widget>[
                      Container(
                        child: Stack(
                          children: <Widget>[
                            Container(
                              child: Icon(
                                Icons.book,
                                color: Colors.redAccent[100],
                                size: 42,
                              ),
                              width: 35,
                              height: 40,
                              alignment: Alignment.center,
                              decoration: BoxDecoration(
                                  borderRadius: BorderRadius.circular(50)),
                              margin: EdgeInsets.only(top: 2, bottom: 2),
                            ),
                            Positioned(
                                right: 0,
                                top: 0,
                                child: Container(
                                  padding: EdgeInsets.fromLTRB(3, 1, 3, 1),
                                  child: Text(
                                    "4",
                                    style: TextStyle(
                                      color: Colors.white,
                                      fontSize: 10,
                                    ),
                                  ),
                                  decoration: BoxDecoration(
                                    color: Colors.redAccent,
                                    borderRadius: BorderRadius.circular(20),
                                  ),
                                ))
                          ],
                        ),
                        color: Colors.black12,
                      ),
                      Container(
                        margin: EdgeInsets.only(left: 10),
                        child: Text(
                          " ",
                          style: TextStyle(
                              color: Colors.black87,
                              fontSize: 14,
                              fontWeight: FontWeight.bold),
                        ),
                      ),
                    ],
                  ),
                ),
                // 
                Expanded(
                  flex: 1, // 1
                  child: Row(
                    mainAxisAlignment: MainAxisAlignment.start,
                    children: <Widget>[
                      Container(
                        child: Stack(
                          children: <Widget>[
                            Container(
                              child: Icon(
                                Icons.book,
                                color: Colors.redAccent[100],
                                size: 42,
                              ),
                              width: 35,
                              height: 40,
                              alignment: Alignment.center,
                              decoration: BoxDecoration(
                                  borderRadius: BorderRadius.circular(50)),
                              margin: EdgeInsets.only(top: 4, bottom: 4),
                            ),
                            Positioned(
                                right: 10,
                                top: 0,
                                child: Container(
                                  padding: EdgeInsets.fromLTRB(3, 1, 3, 1),
                                  child: Text(
                                    "2",
                                    style: TextStyle(
                                      color: Colors.white,
                                      fontSize: 10,
                                    ),
                                  ),
                                  decoration: BoxDecoration(
                                    color: Colors.redAccent,
                                    borderRadius: BorderRadius.circular(20),
                                  ),
                                ))
                          ],
                        ),
                        width: 50,
                      ),
                      Container(
                        margin: EdgeInsets.only(left: 10),
                        child: Text(
                          " ",
                          style: TextStyle(
                              color: Colors.black87,
                              fontSize: 14,
                              fontWeight: FontWeight.bold),
                        ),
                      ),
                    ],
                  ),
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }