Graphical Flutter Widgets architecture and practice

Graphical Flutter Widgets architecture and practice

Overview

The widgets architecture is the first difficult point in learning Flutter. This article does not want to elaborate on the architecture of widges, because it is too theoretical. The main purpose is to let readers understand the following points through theory and practice. The answers to these points will be given at the end of the article

  • From a theoretical level, know how Flutter is laid out?
  • At the application level, quickly screen out the best and optimal layout from the many achievable layouts?
  • The widget can be easily customized

Pre-knowledge

status

Understanding state is a very important part of the design of Flutter Widgets and even the entire Flutter. Let me talk about some of my understandings.

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

  • Data flow causes state changes
  • State is a representation of data
  • The number of states generally represents the complexity of a thing

A few conclusions

Several conclusions can be given from the beginning of the article, and readers will verify by themselves in the following article

  • Flutter's layout is based on Widget, but rendering is based on RenderObject, so some layouts look deep but the actual performance is higher
  • Flutter s layout is based on each
    widget class
    The input of the constructor is (layout data, child widget, callback), and the layout data is used to determine its own UI attributes.
  • Flutter's widgets are divided into three categories, those with no children, those with one child, and those with multiple children.
  • There are more grammatical features in dart just suitable for layout modes like Flutter
  • StatelessWidget means that this Widgets has only one state

How Flutter draws the widget you defined

By this chapter, students should have understood how to layout in Flutter. In this part, we will go deeper from

Framework
Look. In fact, Flutter passed
Widget Tree
Save the layout you wrote in a tree, and then map each node of the tree
Element
The nodes form a tree
Element Tree
, This tree is used to control
Widget Tree
Various states, and finally some
Element Tree
Map one of the nodes on
RenderObject
Types of nodes form a tree
RenderObject Tree
, This tree is responsible for the actual measurement, layout and rendering.

3.trees

in

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

  • Widget Tree
    The dynamic configuration (statefulWidget) used for the overall layout can of course also be a static configuration (statelessWidget)

In-depth:

Widget Tree
It is written by application developers according to business needs, just like the configuration file is set, it will not provide the ability to dynamically manipulate the tree like android or js, but it does not mean that it is
Widget Tree
There is no dynamic capability in Android, just like in Android
.gralde
The file is the same, its configuration is determined according to the input data. It's the same here you can
Widget Tree
Write code similar to if else to provide dynamic capabilities, or let
Widget Tree
With more status. As shown below:

  • Element Tree
    responsible for
    Widget
    Life cycle, managing the parent-child relationship

In-depth: This tree is implemented by Flutter itself, and it provides the ability to dynamically manipulate the tree, such as

mount
Is to add (mount) the root node of the tree,
deactivateChild
Is to remove the child node. In addition to each
Elmenet Tree
The nodes in all hold the corresponding
Widget
,This one
Widget
References for management
Widget
The life cycle of e.g.
initState
,
build
Wait.

  • RenderObject Tree
    Responsible for determining the size and rendering

In-depth: This tree can be compared with Android

View Tree
, Responsible for measurement, layout and rendering, and
Widget Tree
It s not a one-to-one relationship because some
Widget
It is simply to configure the Widget, such as
Expand
, The following figure shows the correspondence between this tree and the two trees above

Measurement, layout and rendering in Flutter

Overview

As mentioned in the previous section

RenderObject Tree
Responsible for measurement, layout and rendering. Among them, the measurement is integrated with the layout in Flutter, and most of the rendering is relatively low-level, so the layout is the core of this piece. After understanding the layout, students can easily select, combine and even optimize various
Widget
This summary mainly talks about the layout

concept
  • Layout direction: same as android, Cartesian coordinate system, the direction is also the origin of the mobile phone (left, top)
  • Main axis: the main layout direction, such as
    Col
    The main layout direction is vertical, so its main axis is vertical
  • Cross axis: another axis in addition to the main axis
  • Tight: Force the width and height of the sub-layout
  • Loose constraint (loose): The size of the sub-layout should be within my control.
Layout process in Flutter

If you understand android in this section

View tree
The layout process of Flutter is particularly easy to understand. The layout process in Flutter is basically the same as in Android.

Resource: Official explanation of the layout process: flutter.cn/docs/develo... The official website explains the layout process in the form of a dialogue is in place, it is recommended to take a look.

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

  • The upper widgetA transfers constraints to the lower widgetB
  • Lower widget B transmits size information to upper widget A
  • The upper widget A determines the position of the lower widget B

We use the following figure to describe

In addition, use the following table to see the similarities and differences between it and android in terms of constraints:

FlutterAndroid
Minimum width: minWidthWidth measurement mode:
widthMode = MesureSpec.getMode(widthMeasureSpec)
Minimum height: minHeightHeight measurement mode:
heightMode = MesureSpec.getMode(heightMeasureSpec)
Maximum width: maxWidthMaximum width:
width = MesureSpec.getSize(widthMeasureSpec).
Maximum height: maxHeightmaximum height:
height = MesureSpec.getSize(heightMeasureSpec)
Comparing observations can be found that there are constraints on the maximum width and height. The difference is the first two items. In fact, the first two items are basically the same, because the width and height measurement modes can be inferred from the values of minWidth and minHeight. The following table lists two equivalent examples
FlutterAndroid
minWidth = 500 & maxWidth = 500
width = 500 & widthMode = EXACLY
minWidth = 0 & maxWidth = double.infinity
width = -1 & widthMode = AT_MOST
  • minWidth = 500 & maxWidth = 500
    Indicates that the width can only be 500
  • maxWidth = double.infinity
    : Indicates that the child can be as large as possible

Let's use the above theory to analyze an example of the official website from the source code 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 conclusion is red

Container
All occupy the parent layout instead of a 150*150 or 10*10 rectangle. The reason is that the ConstrainedBox imposes the constraints of its parent on the child nodes.

Let's look at the reason from the source code and find the corresponding

RenderObject
:
RenderConstrainedBox
And then find
PerformLayout()
function

@override void performLayout() { //ConstrainedBox's parent layout constraints final BoxConstraints constraints = this .constraints; if (child != null ) { //_additionalConstraints are the constraints in the ConstrainedBox parameter, and the enforce function is the cause of the above phenomenon child!. 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 that the value must be between [low]-[high] //minWidth = 150, constraints.minWidth = width of the screen, constraints.maxWidth = width of the screen //so minWidth = width of the screen 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 code

How to solve it? Just wrap a layer of loosely constrained widgets, such as Center

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

We can also look at Center s

performlayout()

///shifted _box.dart -> RenderPositionedBox -> performLayout() @override void performLayout() { final BoxConstraints constraints = this .constraints; if (child != null ) { //loosen function turns constraints into loose constraints child! .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 code

Finally

Col
Take a look at the source code of Widget and summarize the layout process.
Col
corresponding
RenderObject: RenderFlex

void performLayout() { //The constraints passed by the upper Widget of Col final BoxConstraints constraints = this .constraints; //Calculate the size of Col's child and confirm that its own size is _LayoutSizes. This step corresponds to onMeasure() final in android _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 the child in the main axis double childMainPosition = leadingSpace; RenderBox? child = firstChild; while (child != null ) { final FlexParentData childParentData = child.parentData! as FlexParentData; //The child's offset in the cross axis final double childCrossPosition; //Calculate the child's offset in the cross axis 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 save the offset of the child's main axis and the cross axis. This step is equivalent to layout() in android. The specific position of the child can be calculated by this offset 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 is used for final FlexParentData childParentData = child.parentData! as FlexParentData; //Because Col has its own constraints and cannot directly pass the constraints of the Col parent layout to its child layout, so it needs to be reassigned here final BoxConstraints innerConstraints; switch (_direction) { case Axis.horizontal: innerConstraints = BoxConstraints(maxHeight: constraints.maxHeight); break ; case Axis.vertical: //Since the layout direction of `Col` is vertical, I go here. Looking at the source code, we can see that this is a loose constraint. //The constraint given here is the maximum width of the parent layout, which means Col's child The width cannot exceed the width given by the Col parent layout innerConstraints = BoxConstraints(maxWidth: constraints.maxWidth); break ; } //Looking at the function name here, you may mistake it for the layout in android. In fact, this step is only measuring the size of the child final Size childSize = layoutChild(child, innerConstraints); //Calculate the total size of the child that has been measured 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 code

summary:

  • The transfer of constraints is basically the same as the principle in android, that is
    childConstraint = f(SelfConstraint, ParentConstraint)
    ,f stands for function
  • The principle that the parent layout determines its own size is basically the same as the principle in Android, that is, the size of all children is determined first, and then the size of itself can be determined using pseudo code.
    selfSize = f(childs, padding, marigin, layout mode)
  • The parent layout layout itself is a little different from Android, it is called when onLayout in Android
    child.layout(x, y, width, height)
    To locate the position of the child, Flutter only needs to find out in the layout
    x
    ,
    y
    That is in the above source code
    Offset
    And then in
    paint
    Draw this step directly.
  • The layout process comparison diagram at the code level and android is as follows

  • ParentData can store the offset information of the child's birth based on the parent layout and its sibling nodes
  • Just look at the layout process of a Widget and just look at the RenderObject corresponding to the Widget.

Common Widget analysis

When students write the UI layout, every time they choose a Widget, they should think in their hearts how the layout is constrained. After all, Flutter does not have a real-time preview (although it has a hot reload). This actually requires a relatively high level of familiarity with Widgets. Let s use the source code to analyze the common layouts

Widget

Pre-knowledge

There are three types of widgets that need to be rendered

  • Leaf Widget
    LeafRenderObjectWidget
  • With a child
    SingleChildRenderObjectWidget
  • Childish
    MultiChildRenderObjectWidget

Container

  • It is just a composite container of Widget, which does not correspond to RenderObject. There are different widgets under different conditions.
  • Use the simplest combination to customize a widget
///A member attribute in the container corresponds to a Widget, such as alignment -> Align, color -> ColoredBox ///In the case of multiple attributes, it is processed in a nested combination, of course, it involves the order of nesting @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); //... return current!; } Copy code

Adjust the size of the Widget in the layout: Expand

  • Does not belong to the rendering type Widget, the attribute ProxyWidget
  • The effect is equivalent to adding Flex parameters to the child Widget
    ParentData
    , Used to calculate the space that the child Widget should occupy
  • 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 ///The official comment above means Expanded In most cases, it is a child node of Row, Column, and Flex. The purpose is to occupy the remaining space. class Expanded extends Flexible { //The Expanded class is very simple, accepting a flex parameter, which can be compared to the weight attribute const in androidxml 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); ///When the Framework detects that the first rendering type Widget in the Expand package is modified or added, it will call this function @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, this parameter will be used in its 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 is RenderObject) targetParent.markNeedsLayout(); } } } Copy code
  • Diagram

RenderTree
The calculation process is to first calculate the second
Render Widget
The size of the node, and then get the remaining size:
freeSpaceSize
, Based on weight
flex
, Get two edges
Render Widget
Node Size:
freeSpaceSize/2
, Finally from
Row
The first
Render Widget
Start the layout, and the layout style given at the top of the figure is formed. You can see the two edges
Render Widget
The remaining space is scored.

Align

  • Used to adjust the position of sub-components
  • You can adjust your own width and height according to the width and height of the sub-component
  • Use the override method to customize the Widget
  • Center is based on Align
  • Source code analysis
///basic.dart -> Align class Align extends SingleChildRenderObjectWidget { @override RenderPositionedBox createRenderObject(BuildContext context) { return RenderPositionedBox( alignment: alignment, widthFactor: widthFactor, heightFactor: heightFactor, textDirection: Directionality.maybeOf(context), ); } } ///shifted _box.dart -> RenderPositionedBox @override void performLayout() { //Here are the constraints of the parent layout final BoxConstraints constraints = this .constraints; //widthFactor is related to the width and height of Align itself, if it is null, fill it The parent layout, if it is not empty, set a certain width and height for yourself. The following code can be seen final bool shrinkWrapWidth = _widthFactor != null || constraints.maxWidth == double .infinity; final bool shrinkWrapHeight = _heightFactor != null || constraints.maxHeight == double .infinity; if (child != null ) { //After layout, you can get the size of child child!.layout(constraints.loosen(), parentUsesSize: true ); //You can see the role of _widthFactor here size = 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 ); final BoxParentData childParentData = child!.parentData! as BoxParentData; //Determine the position of the child, the logic related to Alignment childParentData.offset = _resolvedAlignment!.alongOffset(size-child!.size as Offset); } Copy code

Scroll layout in Flutter

  • Flutter made a layer of Sliver package for the sliding layout
  • The Sliver component is only used in the scrolling layout
  • Different from other constraint types, the constraint type in the scrolling layout is passed
    SliverConstraints
    , And the initial source of distribution is in
    ViewPort
    corresponding
    RenderViewPort
  • SliverConstraints
    Records include a lot of information such as scroll direction, offset of sub-components, etc.
  • When scrolling, you only need to determine the offset of firstChild and the offset of trailingChild to determine all
    RendSliverList
    The specific location of all the children in, this can be seen through source code analysis
  • Source code analysis

The above figure is the definition of several important areas in ListView. Understand these definitions to understand the source code of ListView. Defined in the following table:

Field/areadefinition
grabage childsRecycle area, similar to Recycle in android RecycleView
remaindExtentThere are similar concepts in android and ios, this area is part of the pre-loaded area
first childWell understood,
RenderSliveList
The first child node in
viewPortWell understood, the area you see
Let's take a look at the key source code
void performLayout() { final SliverConstraints constraints = this .constraints; //Here you can see that the scrollOffset is determined by the upper Widget, the scrolling trigger is triggered by the upper Widget, and the RenderSliveList is only responsible for adjusting the layout of its child View according to the scrolling scrollOffset final double scrollOffset = constraints.scrollOffset + constraints.cacheOrigin; //Here is the reserved space, which is the value of the upper constraints final double remainingExtent = constraints.remainingCacheExtent; earliestUsefulChild = firstChild; //The following loop is to find the Offset of the firstChild after scrolling (for example, when your ListView scrolls up) for ( double earliestScrollOffset = childScrollOffset(earliestUsefulChild!)!; earliestScrollOffset> scrollOffset; earliestScrollOffset = childScrollOffset(earliestUsefulChild)!) { //The insertAndLayoutLeadingChild() function is to look up from the grabage children for item earliestUsefulChild = insertAndLayoutLeadingChild(childConstraints, parentUsesSize: true ); if (earliestUsefulChild == null ) { //... break ; } //Once found, set the offset of childParentData to firstChildScrollOffset directly final double firstChildScrollOffset = 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 + 1, find the next child child = childAfter(child!); final SliverMultiBoxAdaptorParentData childParentData = child!.parentData! as SliverMultiBoxAdaptorParentData; //Assign offset to the child childParentData.layoutOffset = endScrollOffset; endScrollOffset = childScrollOffset(child!)! + paintExtentOf(child!); return true ; } //This loop is the same as advance to determine the Offset from firstChild to trailingChild while (endScrollOffset <scrollOffset) { leadingGarbage += 1 ; if (!advance()) { //Do some recycling logic, skip collectGarbage(leadingGarbage- 1 , 0 ); return ; } } } Copy code

How Flutter is updated
RendObject Tree
of?

The previous chapter only describes the initial state

RendObject Tree
, How does Flutter update
RendObject Tree
Or to update the UI? First look at a gif animation to understand about the four key functions

  • Update entry
    setState()
    : The trigger point for re-executing the build is to indicate
    Widget Tree
    'S status has changed
  • Dirty function
    markNeedToPaint()
    :
    setState
    Need to be right after
    Element Tree
    The node on the top performs the dirty processing from bottom to top, but if it encounters
    isRepaintBoundary == true
    Nodes that are no longer dirty up the table, this feature provides room for optimization to provide Flutter view performance
  • Build a callback function
    build()
    : After the dirty mark is completed, the dirty nodes are checked in order from top to bottom
    build
    , Is to perform your rewrite
    Widget
    of
    build
    function.
  • Diff function
    canUpdate()
    : The build function returns the right
    Widget Tree
    The Diff result, according to the Diff result
    Element Tree
    with
    RedenerObject Tree
    Do the corresponding treatment. The specific logic can be seen in the flow chart below
//Here we judge whether the UI can be updated by judging whether the key and runtimeType are the same static bool canUpdate(Widget oldWidget, Widget newWidget) { return oldWidget.runtimeType == newWidget.runtimeType && oldWidget.key == newWidget.key; } Copy code

How to customize Widget

There are three ways to customize Widget

  • combination
  • cover
  • Fully custom is inherited
    RenderBox

combination

The simplest form of custom Widget, in the source code

Container
That is the form of combination.

cover

  • cover
    CustomSingleChildLayout
    The delegate of the constructor: can be understood as hooking the single-child layout stage

You can see the source code

CustomSingleChildLayout
Also a
SingleChildRenderObjectWidget
, You need to achieve
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 code
  • cover
    CustomMultiChildLayout
    The delegate of the constructor: can be understood as hooking the layout stage of multiple children

The template example is as follows

class MyMultiChildLayoutDelegate extends MultiChildLayoutDelegate { final List < int > layoutIds; MyMultiChildLayoutDelegate( this .layoutIds); @override void performLayout(Size size) { //Here you need to lay out the child yourself, but you can t get the value of the child. Here you can only use layoutId layout //layoutChild(childId, constraints) for ( final layoutId in layoutIds) { layoutChild(layoutId, BoxConstraints().loosen()); } } @override bool shouldRelayout ( covariant MultiChildLayoutDelegate oldDelegate) { throw UnimplementedError(); } } 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 that you need to use the ProxWidget LayoutId to save the id value to the ParentData of MyMultiChildLayoutDelegate. 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 code
  • inherit
    CustomPaint
    It can be understood as hooking the Paint phase, similar to the one in android
    onDraw

View the source code to know

CustomPaint
Is an
SingleChildRenderObjectWidget
, You just need to pass
CustomPainter
An instance of the class

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

The layout practice of Flutter's pit record Dialog is full of screens

summary

It should be said that the Widget architecture is the core of Flutter. It explains from the Framework level how Flutter calculates the position, size and other attributes of each Widget based on the Widget structure defined by the developer. In addition, unlike android, it uses three trees to ensure drawing efficiency, allowing users to actively set areas that do not need to be redrawn to reduce tree traversal. The same as android is the transfer and calculation of layout constraints in the RenderObject Tree. The first principle of all this is that the layout of Flutter is a declarative layout. Finally, answer the three questions raised at the beginning of the article. The end of this article asks: How do you know how Flutter is laid out from a theoretical level? And can improve the accuracy of the layout without real-time preview? Answer: Flutter uses a declarative layout in the user's dimension. The dimension of the Framework is through the cooperation of three trees. The key is to find the PerfromLayout function in the RenderObject corresponding to the Widget. In addition, when you are laying out, you must be clear about the parent layout constraints and the constraints on the child layout, and then determine your own constraints. If you are not familiar with it, you can first look at the official

Widget API
Or check information online. If you still have details, you need to look at the PerfromLayout function. Question: How to quickly filter out the best and optimal layout from the many achievable layouts at the application level? answer:

  • Use the article mentioned in the appropriate scenario (the child layout update does not need to update the parent layout)
    RepaintBoundary
    Widget wraps child Widget
  • Reduce the nesting of rendering Widgets. If you are not familiar with it, you can use the source code to see if the parent class name contains
    Render
    String
  • Through the section "How does Flutter update the RendObject Tree?" you can know that the key is whether canUpdate is triggered, so when writing Widget code, you can encapsulate some Widgets and save them, so that canUpdate returns false to reduce the traversal depth. Or use Dart
    Const
    characteristic. Here is a brief introduction with a demo
class VarDemo { final String name; final int age; //You can use const to modify const VarDemo( this .name, this.age); } void test1() { // const final c1 = const VarDemo("zy", 12); final c2 = const VarDemo("zy", 12); // print(identical(c1, c2)); } main() { test1(); }

widget

RenderObject