An overview of the

The Widgets architecture is the first difficulty in learning about Flutter. This article does not cover Widges architecture because it is too theoretical. It mainly wants to make readers understand the following points through theory and practice. Answers to these points will be given at the end of this article

  • Do you know theoretically how Flutter is arranged?
  • How to quickly filter out the best possible layout at the application level?
  • You can easily customize widgets

Front knowledge

state

Understanding states is an important part of the design of the Flutter Widgets and even the whole Flutter.

Private goods: I usually estimate the workload mainly has two key points state number and data complexity

  • Data flows cause state changes
  • A state is a representation of data
  • The number of states generally represents the complexity of an object

Several conclusions

A few conclusions can be drawn from the beginning of the article, and the reader can verify them for himself

  • The layout of Flutter is based on widgets, but rendering is based on RenderObjects, so some layouts look deep but perform better
  • The layout of Flutter is code based on eachwidget classConstructor, whose input is (layout data, child widgets, callbacks), where the layout data is used to determine its OWN UI properties.
  • There are three types of Flutter widgets: those with no children, those with one child, and those with multiple children.
  • Dart has a number of syntactic features that apply to layouts like Flutter
  • StatelessWidget indicates that the widget has only one state

How does Flutter draw your widgets onto it

By this chapter you should have understood how to layout a Flutter. In this part we will take a deeper look at Flutter from the Framework. Flutter is a Widget Tree that stores your written layout on a Tree and maps each node of the Tree to an Element node to form an Element Tree. This Tree controls the states of the Widget Tree. Finally, some Element Tree nodes map a RenderObject type node to form a RenderObject Tree, which is responsible for the actual measurement, layout, and rendering.

The three trees

There are three trees related to UI layout drawing in the FrameWork, namely [Widget Tree, Element Tree, RenderObject Tree], which can be correspondingly understood as [designer, project manager, construction worker] in a construction project.

  • Widget TreeDynamic configuration (statefulWidget) for the overall layout can also be static configuration (statelessWidget)

Depth: Widget Tree is written by application developers according to business requirements, just like the configuration file is determined. It does not provide the ability to dynamically manipulate the Tree like Android or JS does, but it does not mean that there is no dynamic ability in Widget Tree, just like the.gralde file in Android. Its configuration is determined based on the input data. Here, too, you can write code like if else in Widget Tree to provide dynamic capabilities, or to give Widget Tree more state. The diagram below:

  • Element TreeTo be responsible for theWidgetLife cycle, managing parent-child relationships

Depth: This tree is implemented by the Flutter itself, which provides the ability to dynamically manipulate the tree. For example, mount is to add (mount) the root node of the tree, and deactivateChild is to remove the child node. In addition, each node in the Elmenet Tree holds the corresponding Widget, whose reference is used to manage the Widget’s lifecycle such as initState,build, and so on.

  • RenderObject TreeResponsible for sizing and rendering

Depth: This Tree compares the View Tree in Android, which is responsible for measuring, layout, and rendering. It is not one-to-one with Widget Tree, because some widgets are simply configuration widgets, such as Expand. The following figure shows the relationship between this Tree and the above two trees

Measurement, layout and rendering in Flutter

An overview of the

As mentioned in the previous section, the RenderObject Tree is responsible for measurement, layout, and rendering. The measurement in Flutter is integrated with the layout. Rendering is mostly low-level, so the layout is the core of this piece. With an understanding of layout, students can easily choose, combine and even optimize widgets. This summary focuses on layout

concept
  • Layout direction: The same as Android, cartesian coordinate system, the direction is also mobile phone (left, top) as the origin
  • Main axis: main layout direction, for exampleColThe main layout is portrait so his main axis is portrait
  • Cross axis: an axis other than the main axis
  • Tight: Enforce the width and height of the sublayout
  • Loose: The size of the sub-layout should be within my control
The layout process in Flutter

This section is especially useful if you know the layout flow of the Android View Tree. The layout flow of Flutter is basically the same as that of Android.

Resources: Official explanation of the layout process: flutter.cn/docs/develo… The official website uses the form of dialogue to explain the layout process is in place, recommend to see.

I think the core of the official document is these three sentences:

  • Upper widgetA passes constraints to lower widgetB
  • Lower-layer Widget B passes size information to upper-layer Widget A
  • Upper-layer Widget A determines the location of lower-layer widget B

We use the following picture to describe

In addition, the following table shows the similarities and differences between Android and Android in terms of constraints:

Flutter Android
Minimum width: minWidth Width measurement mode:widthMode = MesureSpec.getMode(widthMeasureSpec)
Minimum height: minHeight Height measurement mode:heightMode = MesureSpec.getMode(heightMeasureSpec)
Maximum width: maxWidth Maximum width:width = MesureSpec.getSize(widthMeasureSpec).
Maximum height: maxHeight Maximum height:height = MesureSpec.getSize(heightMeasureSpec)
By comparison, it can be found that there are constraints on the maximum width and height. The difference is that the first two terms are basically the same in essence, because the measurement mode of width and height can be inferred from the values of minWidth and minHeight. The following table lists two equivalent examples
Flutter Android
minWidth = 500 & maxWidth = 500 width = 500 & widthMode = EXACLY
minWidth = 0 & maxWidth = double.infinity width = -1 & widthMode = AT_MOST
  • minWidth = 500 & maxWidth = 500Indicates a width of 500
  • maxWidth = double.infinity: indicates that the child can be as large as possible

The following uses the above theory to analyze an example of the official website from the source point of view

ConstrainedBox(
    constraints: BoxConstraints(
        minWidth: 150, minHeight: 150, maxWidth: 150, maxHeight: 150),
    child: Container(color: red, width: 10, height: 10),
Copy the code

The result is that the red Container fills the parent layout, not the 150 * 150 or 10 * 10 rectangle, because ConstrainedBox imposes its parent constraint on the child node.

RenderObject: RenderConstrainedBox: PerformLayout(

@override
void performLayout() {
    // ConstrainedBox parent layout constraint
    final BoxConstraints constraints = this.constraints;
    if(child ! =null) {
        //_additionalConstraints is the constraint in ConstrainedBox, and Enforce is the reason for the above phenomenonchild! .layout(_additionalConstraints.enforce(constraints), parentUsesSize:true); size = child! .size; }else {
        size = _additionalConstraints.enforce(constraints).constrain(Size.zero);
    }
}
BoxConstraints enforce(BoxConstraints constraints) {
    return BoxConstraints(
        // clamp means the value must be between [low]-[high]
        // minWidth = 150, constraints. MinWidth = the screen width, constraints. MaxWidth = the screen width
        // So minWidth = screen width
        minWidth: minWidth.clamp(constraints.minWidth, constraints.maxWidth),
        maxWidth: maxWidth.clamp(constraints.minWidth, constraints.maxWidth),
        minHeight: minHeight.clamp(constraints.minHeight, constraints.maxHeight),
        maxHeight: maxHeight.clamp(constraints.minHeight, constraints.maxHeight),
    );
}
Copy the code

So what’s the solution? A widget with a layer of loose constraints, such as Center, will do

Center(
    child: ConstrainedBox(
        constraints: BoxConstraints(
            minWidth: 70, minHeight: 70, maxWidth: 150, maxHeight: 150),
        child: Container(color: red, width: 10, height: 10),),)Copy the code

We can also look at the Center performLayout ()

/// shifted_box.dart -> RenderPositionedBox -> performLayout()
@override
void performLayout() {
    final BoxConstraints constraints = this.constraints;
    if(child ! =null) {
        // loosen the constraints in order to loosen the constraintschild! .layout(constraints.loosen(), parentUsesSize:true); size = constraints.constrain(Size( shrinkWrapWidth ? child! .size.width * (_widthFactor ??1.0) : double.infinity, shrinkWrapHeight ? child! .size.height * (_heightFactor ??1.0) : double.infinity, )); alignChild(); }}Copy the code

Finally, take a look at the source code of ColWidget to summarize the layout process, Col corresponding to RenderObject: RenderFlex

void performLayout() {
    // Constraints passed down from Col's upper-layer widgets
    final BoxConstraints constraints = this.constraints;
    // Calculate the size of Col's child and confirm its own size _LayoutSizes. This step corresponds to onMeasure() in Android.
    final _LayoutSizes sizes = _computeSizes(
      layoutChild: ChildLayoutHelper.layoutChild,
      constraints: constraints,
    );

    final double allocatedSize = sizes.allocatedSize;
    double actualSize = sizes.mainSize;
    double crossSize = sizes.crossSize;
    double maxBaselineDistance = 0.0;
    size = constraints.constrain(Size(crossSize, actualSize));
    actualSize = size.height;
    crossSize = size.width;
    final double actualSizeDelta = actualSize - allocatedSize;
    _overflow = math.max(0.0, -actualSizeDelta);
    final double remainingSpace = math.max(0.0, actualSizeDelta);
    late final double leadingSpace;
    late final double betweenSpace;
    switch (_mainAxisAlignment) {
      case MainAxisAlignment.start:
        leadingSpace = 0.0;
        betweenSpace = 0.0;
        break;
      case MainAxisAlignment.end:
        leadingSpace = remainingSpace;
        betweenSpace = 0.0;
        break;
      case MainAxisAlignment.center:
        leadingSpace = remainingSpace / 2.0;
        betweenSpace = 0.0;
        break;
      case MainAxisAlignment.spaceBetween:
        leadingSpace = 0.0;
        betweenSpace = childCount > 1 ? remainingSpace / (childCount - 1) : 0.0;
        break;
      case MainAxisAlignment.spaceAround:
        betweenSpace = childCount > 0 ? remainingSpace / childCount : 0.0;
        leadingSpace = betweenSpace / 2.0;
        break;
      case MainAxisAlignment.spaceEvenly:
        betweenSpace = childCount > 0 ? remainingSpace / (childCount + 1) : 0.0;
        leadingSpace = betweenSpace;
        break;
    }

    // The offset of child in the main axis
    double childMainPosition = leadingSpace;
    RenderBox? child = firstChild;
    while(child ! =null) {
        final FlexParentData childParentData = child.parentData! as FlexParentData;
        // The offset of child in the cross axis
        final double childCrossPosition;
        // Calculate the offset of child on the cross axis according to the direction of the cross axis layout
        switch (_crossAxisAlignment) {
            case CrossAxisAlignment.start:
            case CrossAxisAlignment.end:
                childCrossPosition = _startIsTopLeft(flipAxis(direction), textDirection, verticalDirection)
                                    == (_crossAxisAlignment == CrossAxisAlignment.start)
                                    ? 0.0
                                    : crossSize - _getCrossSize(child.size);
                break;
            case CrossAxisAlignment.center:
                childCrossPosition = crossSize / 2.0 - _getCrossSize(child.size) / 2.0;
                break;
            case CrossAxisAlignment.stretch:
                childCrossPosition = 0.0;
                break;
            case CrossAxisAlignment.baseline:
                if (_direction == Axis.horizontal) {
                assert(textBaseline ! =null);
                final double?distance = child.getDistanceToBaseline(textBaseline! , onlyReal:true);
                if(distance ! =null)
                    childCrossPosition = maxBaselineDistance - distance;
                else
                    childCrossPosition = 0.0;
                } else {
                childCrossPosition = 0.0;
                }
                break;
        }
        // Use parentData to store the offset of the child's main axis and cross axis. This step is equivalent to android layout(). You can use this offset to calculate the location of the child
        childParentData.offset = Offset(childCrossPosition, childMainPosition);
        child = childParentData.nextSibling;
    }
  }
_LayoutSizes _computeSizes({required BoxConstraints constraints, required ChildLayouter layoutChild}) {
    final double maxMainSize = _direction == Axis.horizontal ? constraints.maxWidth : constraints.maxHeight;
    double crossSize = 0.0;
    double allocatedSize = 0.0;
    RenderBox? child = firstChild;
    RenderBox? lastFlexChild;
    while(child ! =null) {
        / / childParentData used to
        final FlexParentData childParentData = child.parentData! as FlexParentData;
        // Because Col has its own constraints, it cannot pass Col parent constraints directly to its children, so it needs to be reassigned
        final BoxConstraints innerConstraints;
        switch (_direction) {
            case Axis.horizontal:
                innerConstraints = BoxConstraints(maxHeight: constraints.maxHeight);
                break;
            case Axis.vertical:
                // The layout direction of 'Col' is vertical
                One constraint given here is the maximum width of the parent layout, meaning that the width of the Col child cannot exceed the width given by the Col parent layout
                innerConstraints = BoxConstraints(maxWidth: constraints.maxWidth);
                break;
        }
        // The function name is the same as android layout. This step is just to measure the size of child
        final Size childSize = layoutChild(child, innerConstraints);
        // Calculate the total size of the measured child
        allocatedSize += _getMainSize(childSize);
        crossSize = math.max(crossSize, _getCrossSize(childSize));
        child = childParentData.nextSibling;    
    }
   
    final double idealSize = allocatedSize;
     // allocatedSize, crossSize determines its own size
    return _LayoutSizes(
        mainSize: idealSize,
        crossSize: crossSize,
        allocatedSize: allocatedSize,
    );
}
Copy the code

Summary:

  • Constraint passing is basically the same as in AndroidchildConstraint = f(SelfConstraint, ParentConstraint)F stands for function
  • The parent layout determines its own size in much the same way as in Android which is to determine the size of all the children and then determine its own size in pseudocodeSelfSize = f(Childs, padding, Marigin, layout mode)
  • The parent layout itself is a little different from android, which is called when onLayout is calledchild.layout(x, y, width, height)To locate the position of the child, the Flutter is only evaluated in the layoutx.yIn the above source codeOffsetAnd then inpaintI’m going to draw it directly.
  • A comparison of the layout flow at the code level with android is shown below

  • ParentData can store offset information based on the parent layout of the child and its sibling nodes
  • To view the layout flow of a Widget, look directly at the Widget’s RenderObject

Common Widget Analysis

When writing a UI layout, students should think about how the layout is constrained with each Widget they choose. After all, the Flutter doesn’t have a live preview (although it does have a thermal overload). This requires a high level of familiarity with widgets, so let’s use source code to analyze common widgets in your layout

Front knowledge

There are three types of widgets that require a render type

  • Leaves the WidgetLeafRenderObjectWidget
  • With a childSingleChildRenderObjectWidget
  • Many children in theMultiChildRenderObjectWidget

Container

  • It is just a composite class container of widgets, and does not correspond to RenderObject itself. There are different widgets for different conditions
  • Use the simplest combination of ways to customize a widget
/// A member attribute in a Container corresponds to a Widget, such as alignment -> Align, color -> ColoredBox
/// The case of multiple attributes is handled in a nested combination, of course, which involves the nesting order
@override
Widget build(BuildContext context) {
    Widget? current = child;

    // ...

    if(alignment ! =null) current = Align(alignment: alignment! , child: current);final EdgeInsetsGeometry? effectivePadding = _paddingIncludingDecoration;
    if(effectivePadding ! =null)
        current = Padding(padding: effectivePadding, child: current);

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

    returncurrent! ; }Copy the code

Resize the widgets in the layout: Expand

  • Attribute ProxyWidget that is not a rendered Widget
  • This is equivalent to adding Flex parameters to the child WidgetParentDataIs used to calculate how much space the child widgets should take up
  • Source code analysis
/// basic.dart -> Expanded

/// Using an [Expanded] widget makes a child of a [Row], [Column], or [Flex]
/// expand to fill the available space along the main axis
/// Expanded is mostly a child node of Row,Column, and Flex in order to occupy the remaining space
class Expanded extends Flexible {
    // Expanded is extremely simple. It takes a Flex parameter, similar to the weight attribute in AndroidXML
    const Expanded({
        Key? key,
        int flex = 1.required Widget child,
    }) : super(key: key, flex: flex, fit: FlexFit.tight, child: child);
}
/// basic.dart -> Flexible
class Flexible extends ParentDataWidget<FlexParentData> {
    const Flexible({
        Key? key,
        this.flex = 1.this.fit = FlexFit.loose,
        required Widget child,
    }) : super(key: key, child: child);

    /// This function is called when the Framework detects that the Widget of the first render type of the Expand package has been modified or added
    @override
    void applyParentData(RenderObject renderObject) {
        assert(renderObject.parentData is FlexParentData);
        final FlexParentData parentData = renderObject.parentData! as FlexParentData;
        bool needsLayout = false;
        // Put the Flex parameter in parentData, which will be used in the parent layout
        if(parentData.flex ! = flex) { parentData.flex = flex; needsLayout =true;
        }

        if(parentData.fit ! = fit) { parentData.fit = fit; needsLayout =true;
        }

        if (needsLayout) {
        final AbstractNode? targetParent = renderObject.parent;
        if (targetParent isRenderObject) targetParent.markNeedsLayout(); }}}Copy the code
  • The illustration

RenderTreeYou compute the second one firstRender WidgetNode Size, and then get the remaining Size:freeSpaceSize, based on weightflexI get two edgesRender WidgetThe node Size:freeSpaceSize / 2And finally from theRowOne of the firstRender WidgetWhen you start the layout, you form the layout style given at the top of the diagram. You can see the two edgesRender WidgetThe remaining space was graded.

Align

  • Used to adjust the location of child components
  • You can adjust the width and height of your child components
  • Use overrides to customize widgets
  • Based on the Align Center
  • Source code analysis
/// basic.dart -> Align
class Align extends SingleChildRenderObjectWidget {
    @override
    RenderPositionedBox createRenderObject(BuildContext context) {
        returnRenderPositionedBox( alignment: alignment, widthFactor: widthFactor, heightFactor: heightFactor, textDirection: Directionality.maybeOf(context), ); }}/// shifted_box.dart -> RenderPositionedBox
@override
void performLayout() {
    // Here is the constraints of the parent layout
    final BoxConstraints constraints = this.constraints;
    // widthFactor is related to the width and height of the Align itself. If it is null, it fills in the parent layout, and if it is not empty, it sets itself the desired width and height. The following code can be seen
    final boolshrinkWrapWidth = _widthFactor ! =null || constraints.maxWidth == double.infinity;
    final boolshrinkWrapHeight = _heightFactor ! =null || constraints.maxHeight == double.infinity;

    if(child ! =null) {
        After // layout, we can get the size of the childchild! .layout(constraints.loosen(), parentUsesSize:true);
        // Here you can see _widthFactor in actionsize = constraints.constrain(Size( shrinkWrapWidth ? child! .size.width * (_widthFactor ??1.0) : double.infinity, shrinkWrapHeight ? child! .size.height * (_heightFactor ??1.0) : double.infinity,
        ));
        alignChild();
    } else {
        size = constraints.constrain(Size(
        shrinkWrapWidth ? 0.0 : double.infinity,
        shrinkWrapHeight ? 0.0 : double.infinity, )); }}@protected
void alignChild() {
    _resolve();
    assert(child ! =null);
    assert(! child! .debugNeedsLayout);assert(child! .hasSize);assert(hasSize);
    assert(_resolvedAlignment ! =null);
    finalBoxParentData childParentData = child! .parentData!as BoxParentData;
    // Determine the child's position and logic related to AlignmentchildParentData.offset = _resolvedAlignment! .alongOffset(size - child! .sizeas Offset);
}
Copy the code

The rolling layout of a Flutter

  • The Flutter wraps the sliding layout with a layer of Sliver
  • The Sliver component only applies to scrolling layouts
  • Unlike other constraint types, constraint types in a scrolling layout are passedSliverConstraints, and the original source of distribution is inViewPortThe correspondingRenderViewPort
  • SliverConstraintsRecords include a lot of information about scroll direction, offsets of child components, and so on
  • All you need to do when scrolling is determine the offset of firstChild and the offset of trailingChildRendSliverListThe specific location of all children in the
  • Source code analysis

Above is the ListView of several important area definition, understand these several definitions will be a good understanding of ListView source. The following table defines:

Field/area define
grabage childs Recycle area, similar to Recycle in Android RecycleView
remaindExtent A similar concept exists in Android and ios, where this area is part of a pre-loaded area
first child It’s easy to understand,RenderSliveListThe first child node in
viewPort It’s easy to understand, the area you see
Take a look at the key source code
void performLayout() {
    final SliverConstraints constraints = this.constraints;
    RenderSliveList is only responsible for adjusting the layout of its child Views according to the scrollOffset. RenderSliveList is responsible for adjusting the layout of its child Views according to the scrollOffset
    final double scrollOffset = constraints.scrollOffset + constraints.cacheOrigin;
    // The reserved space is the upper constraint value
    final double remainingExtent = constraints.remainingCacheExtent;
    earliestUsefulChild = firstChild;
    // The loop below is to find the Offset of the scrolling firstChild (e.g., when you scroll up the ListView)
    for (doubleearliestScrollOffset = childScrollOffset(earliestUsefulChild!) ! ; earliestScrollOffset > scrollOffset; earliestScrollOffset = childScrollOffset(earliestUsefulChild)!) {/ / insertAndLayoutLeadingChild () function is upward from the grabage childs in the search for the item
            earliestUsefulChild = insertAndLayoutLeadingChild(childConstraints, parentUsesSize: true);
            if(earliestUsefulChild == null) {
                / /...
                break;
            }
            // Set childParentData offset to firstChildScrollOffset
            final doublefirstChildScrollOffset = earliestScrollOffset - paintExtentOf(firstChild!) ;final SliverMultiBoxAdaptorParentData childParentData = earliestUsefulChild.parentData! as SliverMultiBoxAdaptorParentData;
            childParentData.layoutOffset = firstChildScrollOffset;
        }
    RenderBox? child = earliestUsefulChild;
    double endScrollOffset = childScrollOffset(child)! + paintExtentOf(child);
    bool advance() { 
        // index + 1child = childAfter(child!) ;finalSliverMultiBoxAdaptorParentData childParentData = child! .parentData!as SliverMultiBoxAdaptorParentData;
        // Assign offset to the childchildParentData.layoutOffset = endScrollOffset; endScrollOffset = childScrollOffset(child!) ! + paintExtentOf(child!) ;return true;
    }

    // Advance sets the Offset from firstChild to trailingChild
    while (endScrollOffset < scrollOffset) {
      leadingGarbage += 1;
      if(! advance()) {// Do some recycling logic, which is skipped here
        collectGarbage(leadingGarbage - 1.0);
        return; }}}Copy the code

How does Flutter updateRendObject Tree?

The previous section describes only the initial stateRendObject TreeHow does Flutter updateRendObject TreeOr what about updating the UI? Let’s see a GIF to get the ideaThe key four functions

  • Update the entrancesetState(): indicates the trigger point for re-executing buildWidget TreeThe state has changed
  • The dirty functionmarkNeedToPaint(): setStateAfter the need forElement TreeThe node on is marked from bottom to top, but if encounteredisRepaintBoundary == trueThis property provides room to optimize the performance of the Flutter view
  • Build the callback functionbuild(): After the dirty nodes are marked, they are marked from top to bottombuildIs to perform your rewriteWidgetthebuildFunction.
  • Diff functioncanUpdate()The: build function returns pairwiseWidget TreeAccording to the Diff results ofElement TreeandRedenerObject TreeDeal with it accordingly. The specific logic can be seen in the following flow chart
 // The UI can be updated by checking whether the key and runtimeType are consistent
  static bool canUpdate(Widget oldWidget, Widget newWidget) {
    return oldWidget.runtimeType == newWidget.runtimeType
        && oldWidget.key == newWidget.key;
  }
Copy the code

How do I customize widgets

There are three ways to customize widgets

  • combination
  • cover
  • Full customization is inheritanceRenderBox

combination

The simplest form of custom Widget is the Container in the source code.

cover

  • coverCustomSingleChildLayoutThe constructor’s delegate: can be interpreted as a hook to a single child’s layout stage

View the source code can be known CustomSingleChildLayout is also a SingleChildRenderObjectWidget, you need to implement SingleChildLayoutDelegate


class MySingleChildLayoutDelegate extends SingleChildLayoutDelegate {
  @override
  bool shouldRelayout(covariant SingleChildLayoutDelegate oldDelegate) {
    throw UnimplementedError();
  }

  @override
  Size getSize(BoxConstraints constraints) {
    return super.getSize(constraints);
  }

  @override
  Offset getPositionForChild(Size size, Size childSize) {
    // The offset of the child based on the parent layout
    return super.getPositionForChild(size, childSize);
  }

  @override
  BoxConstraints getConstraintsForChild(BoxConstraints constraints) {
    return super.getConstraintsForChild(constraints); }}class CustomLayoutRoute extends StatelessWidget {
    @override
    Widget build(BuildContext context) {
        return Center(
            child: CustomSingleChildLayout (
                delegate: MySingleChildLayoutDelegate(),
                child: Text("123"))); }}Copy the code
  • coverCustomMultiChildLayoutThe constructor’s delegate: can be interpreted as a hook to the multi-child layout stage

The template example is as follows

class MyMultiChildLayoutDelegate extends MultiChildLayoutDelegate {
  final List<int> layoutIds;
  MyMultiChildLayoutDelegate(this.layoutIds);

  @override
  void performLayout(Size size) {
    // We need to layout the child, but we can't get the value of the child. Only layoutId can be used
    // layoutChild(childId, constraints)
    for (final layoutId inlayoutIds) { layoutChild(layoutId, BoxConstraints().loosen()); }}@override
  bool shouldRelayout(covariant MultiChildLayoutDelegate oldDelegate) {
    throwUnimplementedError(); }}class CustomMultiRoute extends StatelessWidget {
  final layoutIds = [1.2.3.4];
  @override
  Widget build(BuildContext context) {
    return Center(
      child: CustomMultiChildLayout(
        delegate: MyMultiChildLayoutDelegate([1.2.3.4]),
        children: [
          / / note here need to use LayoutId this ProxWidget will save id value to MyMultiChildLayoutDelegate ParentData
          LayoutId(id: layoutIds[0], child: Text("0")),
          LayoutId(id: layoutIds[1], child: Text("1")),
          LayoutId(id: layoutIds[2], child: Text("2")),
          LayoutId(id: layoutIds[3], child: Text("3")),,),); }}Copy the code
  • inheritanceCustomPaintHook the Paint phase, like in AndroidonDraw

View source known CustomPaint is a SingleChildRenderObjectWidget, you only need to preach CustomPainter instance of a class

class CustomPaintRoute extends StatelessWidget {
    @override
    Widget build(BuildContext context) {
        return Center(
            child: CustomPaint(
                size: Size(300.300), // Specify the canvas sizepainter: MyPainter(), ), ); }}class MyPainter extends CustomPainter {
     @override
  void paint(Canvas canvas, Size size) {
      // Business logic}}Copy the code

Layout of the practiceThe Dialog of the Flutter pit log is full screen 😭

conclusion

The Widget architecture is the core of Flutter. It explains how Flutter calculates the position, size, and other properties of each Widget based on the Widget structure defined by the developer. In addition, unlike Android, it uses three trees to ensure the efficiency of drawing, allowing users to take the initiative to set areas that do not need to be redrawn to reduce tree traversal. As with Android, layout constraints are passed and computed in the RenderObject Tree. The first principle of all this is that the layout of Flutter is declarative. Finally, I will answer the three questions raised at the beginning of this article and conclude by asking: how is Flutter arranged theoretically? And be able to improve layout accuracy without a live preview? A: Flutter has a declarative layout in the user’s dimension. In the Framework dimension, the three trees work together. The key is to find the PerfromLayout function in the RenderObject corresponding to the Widget. In addition, you should think about the parent layout constraints and the constraints on the child layout, and then determine your own constraints. If you are not familiar with them, check the official Widget API or look up information online. If you have any more details you need to look at the PerfromLayout function. Q: How to quickly select the best layout from the many possible layouts at the application level? A:

  • Use this article in appropriate scenarios (child layout updates do not update parent layout)RepaintBoundaryWidgets Wrap child widgets
  • Reduce nesting of rendered widgets and, if not familiar, check the source code to see if the parent class name is includedRenderstring
  • How does Flutter update the RendObject Tree? As you can see from this section, the key is whether canUpdate fires, so when writing Widget code, you can wrap some widgets and save them so that canUpdate returns false, reducing the traversal depth. Or DartConstFeatures. So let’s do a quick demo here
class VarDemo {
  final String name;
  final int age;
  // Can be decorated with const
  const VarDemo(this.name, this.age);
}

void test1() {
  / / add const
  final c1 = const VarDemo("zy".12);
  final c2 = const VarDemo("zy".12);
  // Check if it is the same instance
  print(identical(c1, c2));
}

main() {
  test1();
}
Copy the code

Q: How can I easily customize widgets? A: There are three ways to do this: choose composition first, override second, or inherit RenderObject if you need too much personalization