Flutter layout construction

1. Introduction to Layout Components

Layout class components refer to Widgets that directly or indirectly inherit (include) SingleChildRenderObjectWidgetand , and they generally have a child or children property for receiving sub-Widgets. MultiChildRenderObjectWidgetLet's look at the inheritance relationship
Widget > RenderObjectWidget > (Leaf/SingleChild/MultiChild)RenderObjectWidget.

Layout Build List

Widget illustrate use
LeafRenderObjectWidget Base class for non-container components The leaf node of the Widget tree is used for widgets without child nodes. Usually, the basic components belong to this category, such as Image.
SingleChildRenderObjectWidget Single child component base class Contains a sub-Widget, such as: ConstrainedBox, DecoratedBox, etc.
MultiChildRenderObjectWidget Multi-child component base class Contains multiple sub-Widgets, and generally has a children parameter that accepts an array of Widgets. Such as Row, Column, Stack, etc.

2. Understand Flutter layout constraints

Flutter layout rules:

First, the upper-level widgets pass constraints to the lower-level widgets;
then, the lower-level widgets pass size information to the upper-level widgets.
Finally, the upper-level widget determines the position of the lower-level widget.

For example, the constraint passed from the parent component to the child component is "the maximum width and height cannot exceed 100, and the minimum width and height is 0". If we set the width and height of the child component to 200, the final size of the child component is 100*100, because At any time, the child component must first abide by the constraints of the parent component, and then apply the constraints of the child component on this basis (equivalent to finding an intersection between the constraints of the parent component and its own size).

3. Linear layout (Row and Column)

Linear layout refers to arranging subcomponents horizontally or vertically. Linear layouts are implemented in Flutter through Rowand , similar to controls Columnin Android . and both inherit from .LinearLayoutRowColumnFlex

1. Main axis and longitudinal axis

For linear layout, there are main axis and vertical axis. If the layout is along the horizontal direction, then the main axis refers to the horizontal direction, and the vertical axis refers to the vertical direction; if the layout is along the vertical direction, then the main axis refers to the vertical direction, and the vertical axis is horizontal direction. MainAxisAlignmentIn the linear layout, there are two enumeration classes and which define the alignment CrossAxisAlignment, which represent the main axis alignment and vertical axis alignment respectively.

2. Row

Row can arrange its child widgets horizontally.

Row({
  ...  
  TextDirection textDirection,    
  MainAxisSize mainAxisSize = MainAxisSize.max,    
  MainAxisAlignment mainAxisAlignment = MainAxisAlignment.start,
  VerticalDirection verticalDirection = VerticalDirection.down,  
  CrossAxisAlignment crossAxisAlignment = CrossAxisAlignment.center,
  List<Widget> children = const <Widget>[],
})

textDirection: Indicates the layout order of subcomponents in the horizontal direction (from left to right or from right to left), and defaults to the text direction of the current Locale environment of the system (for example, Chinese and English are from left to right, while Arabic is from right to left Left).
mainAxisSize: Indicates the space occupied by the Row in the main axis (horizontal) direction. By default MainAxisSize.max, it means occupying as much space as possible in the horizontal direction. At this time, no matter how much horizontal space the sub-widgets actually occupy, the width of the Row is always equal to the maximum width in the horizontal direction; and MainAxisSize.min means occupying as little horizontal space as possible. When sub-components do not occupy the remaining horizontal space, the actual width of Row is equal to the horizontal space occupied by all sub-components; : Indicates the alignment of sub-components
mainAxisAlignmentin the horizontal space occupied by Row , if the mainAxisSize value is MainAxisSize.min, this property is meaningless, because the width of the child component is equal to the width of the Row. Only when the value of mainAxisSize MainAxisSize.maxis meaningful, this property MainAxisAlignment.startmeans textDirectionalignment along the initial direction. If textDirectionthe value TextDirection.ltris set , it MainAxisAlignment.startmeans left alignment. When the value of textDirection is TextDirection.rtl, it means right alignment. MainAxisAlignment.end and MainAxisAlignment.start are just the opposite; MainAxisAlignment.center means center alignment. Readers can understand this: textDirection is the reference system of mainAxisAlignment.
verticalDirection: Indicates the alignment direction of the Row vertical axis (vertical), the default is VerticalDirection.down, which means from top to bottom.
crossAxisAlignment: Indicates the alignment of the subcomponent in the vertical axis direction. The height of Row is equal to the height of the highest subelement in the subcomponent. Its value is the same as MainAxisAlignment(including three values ​​of start, end and center). The difference iscrossAxisAlignmentThe reference system of is verticalDirection, that is, when verticalDirectionthe value is top-aligned, and when the value is bottom-aligned; and is just the opposite; : an array of subcomponents. **Example**VerticalDirection.downcrossAxisAlignment.startverticalDirectionVerticalDirection.upcrossAxisAlignment.startcrossAxisAlignment.endcrossAxisAlignment.start
children


class RowShowPage extends StatelessWidget {
  const RowShowPage({super.key});

  @override
  Widget build(BuildContext context) {
    return Column(
      //测试Row对齐方式,排除Column默认居中对齐的干扰
      crossAxisAlignment: CrossAxisAlignment.start,
      children: <Widget>[
        Row(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(" hello world "),
            Text(" I am Jack "),
          ],
        ),
        Row(
          mainAxisSize: MainAxisSize.min,
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(" hello world "),
            Text(" I am Jack "),
          ],
        ),
        Row(
          mainAxisAlignment: MainAxisAlignment.end,
          textDirection: TextDirection.rtl,
          children: <Widget>[
            Text(" hello world "),
            Text(" I am Jack "),
          ],
        ),
        Row(
          crossAxisAlignment: CrossAxisAlignment.start,
          verticalDirection: VerticalDirection.up,
          children: <Widget>[
            Text(" hello world ", style: TextStyle(fontSize: 30.0),),
            Text(" I am Jack "),
          ],
        ),
      ],
    );
  }
}

insert image description here

Explanation: The first Row is very simple, and the default is center alignment; the
second Row, since the mainAxisSize value is MainAxisSize.min, the width of the Row is equal to the sum of the widths of the two Texts, so the alignment is meaningless, so it will go from left to right Right display;
the third Row sets the textDirection value to TextDirection.rtl, so the sub-components will be arranged in order from right to left, and at this time MainAxisAlignment.end means left alignment, so the final display result is the third row in the figure;
The fourth Row tests the alignment of the vertical axis. Since the fonts of the two sub-Texts are different, their heights are also different. We specify the verticalDirection value as VerticalDirection.up, that is, arrange from low to top, and at this time the crossAxisAlignment value is CrossAxisAlignment.start means bottom alignment.

3. Column

Column can arrange its child components vertically. The parameters are the same as Row, the difference is that the layout direction is vertical, and the vertical axis of the main axis is just opposite. Readers can understand it by analogy with Row. Let's look at an example:

class CenterColumnRoute extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.center,
      children: <Widget>[
        Text("hi"),
        Text("world"),
      ],
    );
  }
}

insert image description here

Explanation:
Since we did not specify the mainAxisSize of the Column, so if the default value MainAxisSize.max is used, the Column will occupy as much space as possible in the vertical direction, in this case it will occupy the entire height of the screen.
Since we specified the crossAxisAlignment property as CrossAxisAlignment.center, the subitems will be centered in the vertical axis direction of the Column (horizontal direction at this time). Note that alignment in the horizontal direction is bounded, and the total width is the actual width of the space occupied by the Column, and the actual width depends on the Widget with the largest width among the children. In this example, Column has two sub-widgets, and the Text displaying "world" has the largest width, so the actual width of Column is the width of Text("world"), so Text("hi") will be displayed on The middle part of Text("world").
In fact, both Row and Column will only occupy as much space as possible in the direction of the main axis, and the length of the vertical axis depends on the length of their largest child element. If we want the two text controls in this example to be aligned in the middle of the entire phone screen, we have two methods:

Specify the width of the Column to be the screen width; this is simple, and we can force the width limit to change with ConstrainedBoxor , for example:SizedBox

ConstrainedBox(
  constraints: BoxConstraints(minWidth: double.infinity), 
  child: Column(
    crossAxisAlignment: CrossAxisAlignment.center,
    children: <Widget>[
      Text("hi"),
      Text("world"),
    ],
  ),
);

Set minWidthto double.infinity, to make the width take up as much space as possible.

4. Special circumstances

If Row is nested inside Row, or Column is nested inside Column, then only the outermost Row or Column will occupy as much space as possible, and the space occupied by Row or Column inside is the actual size. The following uses Column as an example to illustrate:

Container(
  color: Colors.green,
  child: Padding(
    padding: const EdgeInsets.all(16.0),
    child: Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      mainAxisSize: MainAxisSize.max, //有效,外层Colum高度为整个屏幕
      children: <Widget>[
        Container(
          color: Colors.red,
          child: Column(
            mainAxisSize: MainAxisSize.max,//无效,内层Colum高度为实际高度  
            children: <Widget>[
              Text("hello world "),
              Text("I am Jack "),
            ],
          ),
        )
      ],
    ),
  ),
);

If you want the inner Column to fill up the outer Column, you can use the Expanded component:

Expanded( 
  child: Container(
    color: Colors.red,
    child: Column(
      mainAxisAlignment: MainAxisAlignment.center, //垂直方向居中对齐
      children: <Widget>[
        Text("hello world "),
        Text("I am Jack "),
      ],
    ),
  ),
)

4. Flexible layout (Flex and Expanded)

Flexible layout allows child components to allocate parent container space according to a certain ratio. The elastic layout in Flutter is mainly realized through the cooperation of Flex and Expanded.

1. Flex

FlexComponents can arrange subcomponents along the horizontal or vertical direction, Row and Column are inherited from Flex.

Flex({
  ...
  required this.direction, //弹性布局的方向, Row默认为水平方向,Column默认为垂直方向
  List<Widget> children = const <Widget>[],
})

2. Expanded

Expanded can only be used as a child of Flex (otherwise an error will be reported), and it can "expand" the space occupied by the Flex child component in proportion. Since both Row and Column inherit from Flex, Expanded can also be their child.

const Expanded({
  int flex = 1, 
  required Widget child,
})

flex:The parameter is the elastic coefficient. If it is 0 or null, the child is inelastic, that is, the space that will not be occupied by expansion. If greater than 0, all Expandeds divide the entire free space of the main axis according to their flex ratio.


class FlexLayoutTestRoute extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Column(
      children: <Widget>[
        //Flex的两个子widget按1:2来占据水平空间
        Flex(
          direction: Axis.horizontal,
          children: <Widget>[
            Expanded(
              flex: 1,
              child: Container(
                height: 30.0,
                color: Colors.red,
              ),
            ),
            Expanded(
              flex: 2,
              child: Container(
                height: 30.0,
                color: Colors.green,
              ),
            ),
          ],
        ),
        Padding(
          padding: const EdgeInsets.only(top: 20.0),
          child: SizedBox(
            height: 100.0,
            //Flex的三个子widget,在垂直方向按2:1:1来占用100像素的空间
            child: Flex(
              direction: Axis.vertical,
              children: <Widget>[
                Expanded(
                  flex: 2,
                  child: Container(
                    height: 30.0,
                    color: Colors.red,
                  ),
                ),
                Spacer(
                  flex: 1,
                ),
                Expanded(
                  flex: 1,
                  child: Container(
                    height: 30.0,
                    color: Colors.green,
                  ),
                ),
              ],
            ),
          ),
        ),
      ],
    );
  }
}

insert image description here

Five, flow layout (Wrap and Flow)

Row has only one line by default, and it will not wrap if it exceeds the screen. We call a layout that automatically wraps beyond the screen display range as a fluid layout. Flutter supports fluid layout through Wrapand .Flow

1. Wrap

定义
Wrap({
  ...
  this.direction = Axis.horizontal,
  this.alignment = WrapAlignment.start,
  this.spacing = 0.0,
  this.runAlignment = WrapAlignment.start,
  this.runSpacing = 0.0,
  this.crossAxisAlignment = WrapCrossAlignment.start,
  this.textDirection,
  this.verticalDirection = VerticalDirection.down,
  List<Widget> children = const <Widget>[],
})

We can see that many properties of Wrap are also available in Row (including Flex and Column), such as direction, crossAxisAlignment, textDirection, verticalDirection, etc. The meaning of these parameters is the same, and we will not repeat the introduction. We can think that Wrapand Flex(including Row and Column) are basically the same except that Wrap will wrap when it exceeds the display range. Let's take a look at several properties unique to Wrap:

spacing: The spacing of sub-widgets in the main axis direction
runSpacing: The spacing in the vertical axis direction
runAlignment: The alignment in the vertical axis direction

Here's an example:

 Wrap(
   spacing: 8.0, // 主轴(水平)方向间距
   runSpacing: 4.0, // 纵轴(垂直)方向间距
   alignment: WrapAlignment.center, //沿主轴方向居中
   children: <Widget>[
     Chip(
       avatar: CircleAvatar(backgroundColor: Colors.blue, child: Text('A')),
       label: Text('Hamilton'),
     ),
     Chip(
       avatar: CircleAvatar(backgroundColor: Colors.blue, child: Text('M')),
       label: Text('Lafayette'),
     ),
     Chip(
       avatar: CircleAvatar(backgroundColor: Colors.blue, child: Text('H')),
       label: Text('Mulligan'),
     ),
     Chip(
       avatar: CircleAvatar(backgroundColor: Colors.blue, child: Text('J')),
       label: Text('Laurens'),
     ),
   ],
)

insert image description here

2. Flow

Generally, we rarely use Flow, because it is too complicated and needs to realize the position conversion of sub-widgets by ourselves. In many scenarios, the first thing to consider is whether Wrap meets the requirements. Flow is mainly used in some scenarios that require custom layout strategies or high performance requirements (such as in animation).
Flow has the following advantages:

  • Good performance; Flow is a very efficient control for adjusting the size and position of sub-components. Flow uses the transformation matrix to optimize the position adjustment of sub-components: after Flow is positioned, if the size or position of sub-components changes , the method FlowDelegatein is called to redraw, and the transformation matrix is ​​used when redrawing, and the component position is not actually adjusted.paintChildren()context.paintChildcontext.paintChild
  • Flexible; since we need to implement the paintChildren() method of FlowDelegate by ourselves, we need to calculate the position of each component by ourselves, so we can customize the layout strategy.

shortcoming:

  • Complicated to use.
  • Flow cannot adapt to the size of subcomponents, and must return a fixed size by specifying the size of the parent container or implementing getSize of TestFlowDelegate.

Example:

We customize the flow layout of six color blocks:

class FlowTestRoute extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return Flow(
      delegate: TestFlowDelegate(margin: EdgeInsets.all(10.0)),
      children: <Widget>[
        Container(width: 80.0, height:80.0, color: Colors.red,),
        Container(width: 80.0, height:80.0, color: Colors.green,),
        Container(width: 80.0, height:80.0, color: Colors.blue,),
        Container(width: 80.0, height:80.0,  color: Colors.yellow,),
        Container(width: 80.0, height:80.0, color: Colors.brown,),
        Container(width: 80.0, height:80.0,  color: Colors.purple,),
      ],
    );
  }
}

/// 实现TestFlowDelegate:
class TestFlowDelegate extends FlowDelegate {
  EdgeInsets margin;

  TestFlowDelegate({this.margin = EdgeInsets.zero});

  double width = 0;
  double height = 0;

  @override
  void paintChildren(FlowPaintingContext context) {
    var x = margin.left;
    var y = margin.top;
    //计算每一个子widget的位置
    for (int i = 0; i < context.childCount; i++) {
      var w = context.getChildSize(i)!.width + x + margin.right;
      if (w < context.size.width) {
        context.paintChild(i, transform: Matrix4.translationValues(x, y, 0.0));
        x = w + margin.left;
      } else {
        x = margin.left;
        y += context.getChildSize(i)!.height + margin.top + margin.bottom;
        //绘制子widget(有优化)
        context.paintChild(i, transform: Matrix4.translationValues(x, y, 0.0));
        x += context.getChildSize(i)!.width + margin.left + margin.right;
      }
    }
  }

  @override
  Size getSize(BoxConstraints constraints) {
    // 指定Flow的大小,简单起见我们让宽度尽可能大,但高度指定为200,
    // 实际开发中我们需要根据子元素所占用的具体宽高来设置Flow大小
    return Size(double.infinity, 200.0);
  }

  @override
  bool shouldRepaint(FlowDelegate oldDelegate) {
    return oldDelegate != this;
  }
}

insert image description here

It can be seen that our main task is to realize paintChildren, and its main task is to determine the position of each sub-widget. Since Flow cannot adapt to the size of sub-widgets, we specify the size of Flow by returning a fixed size in getSize.

Note that if we need to customize the layout strategy, the general preferred way is to directly inherit RenderObject, and then implement it by rewriting performLayout.

6. Cascade layout (Stack, Positioned)

Cascading layouts are similar to absolute positioning in the Web and Frame layouts in Android. Subcomponents can determine their own positions based on their distance from the four corners of the parent container. Cascading layouts allow child components to be stacked in the order they are declared in the code. StackThese two components are used in Flutter Positionedto cooperate with absolute positioning. Stack allows sub-components to be stacked and Positionedis used Stackto determine the position of sub-components according to the four corners.

1. Stack

The Stack component is defined as follows:

Stack({
  this.alignment = AlignmentDirectional.topStart,
  this.textDirection,
  this.fit = StackFit.loose,
  this.clipBehavior = Clip.hardEdge,
  List<Widget> children = const <Widget>[],
})

alignment: This parameter determines how to align unpositioned (not used Positioned) or partially positioned subcomponents. The so-called partial positioning here specifically refers to not positioning on a certain axis: left and right are horizontal axes, top and bottom are vertical axes, as long as a positioning attribute on a certain axis is included, it is considered to be positioned on that axis.
textDirection: The same function as Row and Wrap textDirection, both are used to determine the reference system of alignment alignment, that is, if the value of textDirection is TextDirection.ltr, then the start of alignment represents left, and end represents right, that is, the order from left to right; the value of textDirection If the value is TextDirection.rtl, the start of the alignment represents the right, and the end represents the left, that is, the order from right to left.
fit: This parameter is used to determine how unpositioned subcomponents fit into the size of the Stack. StackFit.looseIndicates that the size of the subcomponent is used, and StackFit.expand indicates that it is expanded to the size of the Stack.
clipBehavior: This attribute determines how to clip the part that exceeds the display space of the Stack. The clipping method is defined in the Clip enumeration class, which Clip.hardEdgemeans direct clipping without anti-aliasing. For more information, please refer to the source code comments.

2. Positioned

The default constructor for Positioned is as follows:

const Positioned({
  Key? key,
  this.left, 
  this.top,
  this.right,
  this.bottom,
  this.width,
  this.height,
  required Widget child,
})

left, top, right, and bottom represent the distances from the left, top, right, and bottom sides of the Stack, respectively. Width and height are used to specify the width and height of the element to be positioned. Note that the meaning of the width and height of Positioned is slightly different from other places. It is used here to position components with left, top, right, and bottom. For example, in the horizontal direction, you can only specify left, right, and width For two of the three attributes, if you specify left and width, right will automatically calculate (left+width). If you specify three attributes at the same time, an error will be reported. The same is true for the vertical direction.

**Example**
In the following example, we demonstrate the features of Stack and Positioned by positioning several Text components:

//通过ConstrainedBox来确保Stack占满屏幕
class PositionTestRoute extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return ConstrainedBox(
      constraints: BoxConstraints.expand(),
      child: Stack(
        alignment:Alignment.center , //指定未定位或部分定位widget的对齐方式
        children: <Widget>[
          Container(
            child: Text("Hello world",style: TextStyle(color: Colors.white)),
            color: Colors.red,
          ),
          Positioned(
            left: 18.0,
            child: Text("I am Jack"),
          ),
          Positioned(
            top: 18.0,
            child: Text("Your friend"),
          )
        ],
      ),
    );
  }
}

insert image description here

Since the first child text component Text("Hello world") has no positioning specified, and the alignment value is Alignment.center, it will be displayed in the center. The second sub-text component Text ("I am Jack") only specifies the horizontal positioning (left), so it belongs to partial positioning, that is, there is no positioning in the vertical direction, then its vertical alignment will follow the alignment specified Alignment is aligned, that is, vertically centered. For the third subtext component Text("Your friend"), the principle is the same as that of the second Text, except that the horizontal direction is not positioned, and the horizontal direction is centered.

We assign a fit attribute to the Stack in the above example, and then adjust the order of the three subtext components:

Stack(
  alignment:Alignment.center ,
  fit: StackFit.expand, //未定位widget占满Stack整个空间
  children: <Widget>[
    Positioned(
      left: 18.0,
      child: Text("I am Jack"),
    ),
    Container(child: Text("Hello world",style: TextStyle(color: Colors.white)),
      color: Colors.red,
    ),
    Positioned(
      top: 18.0,
      child: Text("Your friend"),
    )
  ],
),

insert image description here

It can be seen that since the second child text component is not positioned, fitthe attribute will work on it, and it will fill up the Stack. Since the Stack child elements are stacked, the first child text component is covered by the second one, and the third one is on the top layer, so it can be displayed normally.

Seven, alignment and relative positioning (Align)

1. Align

The Align component can adjust the position of subcomponents, defined as follows:

Align({
  Key key,
  this.alignment = Alignment.center,
  this.widthFactor,
  this.heightFactor,
  Widget child,
})

alignment: Requires a AlignmentGeometryvalue of type indicating the starting position of the child component in the parent component. AlignmentGeometry is an abstract class that has two commonly used subclasses: Alignment and FractionalOffset, which we will describe in detail in the examples below.
widthFactorand heightFactorare attributes used to determine the width and height of the Align component itself; they are two scaling factors that will be multiplied by the width and height of the child element respectively, and the final result is the width and height of the Align component. If the value is null, the component's width and height will take up as much space as possible.
example

Container(
  height: 120.0,
  width: 120.0,
  color: Colors.blue.shade50,
  child: Align(
    alignment: Alignment.topRight,
    child: FlutterLogo(
      size: 60,
    ),
  ),
)

insert image description here

FlutterLogo is a component provided by the Flutter SDK, and its content is the logo of Flutter.

In the above example, we explicitly specified the width and height of the Container as 120. If we do not explicitly specify the width and height, the same effect can be achieved by specifying both widthFactor and heightFactor as 2: because the width and height of FlutterLogo are 60, the final width and height of Align are both 2*60=120.

Align(
  widthFactor: 2,
  heightFactor: 2,
  alignment: Alignment.topRight,
  child: FlutterLogo(
    size: 60,
  ),
),

2. Alignment

Alignment is inherited from AlignmentGeometryand represents a point within a rectangle. It has two attributes x and y, which represent the offset in the horizontal and vertical directions respectively. Alignment is defined as follows:

Alignment(this.x, this.y)

The Alignment Widget will take the center point of the rectangle as the coordinate origin, ie Alignment(0.0, 0.0) . The values ​​of x and y from -1 to 1 respectively represent the distance from the left to the right of the rectangle and the distance from the top to the bottom, so 2 horizontal (or vertical) units are equal to the width (or height) of the rectangle, such as Alignment(-1.0, -1.0) represents the left vertex of the rectangle, and Alignment(1.0, 1.0) represents the right bottom end point, and Alignment(1.0, -1.0) is the right vertex, Alignment.topRight. For ease of use, the origin, four vertices, and end points of the four sides of the rectangle have been defined as static constants in the Alignment class.

Alignment can convert its coordinates to specific offset coordinates of child elements through its coordinate conversion formula:

实际偏移 = (Alignment.x * (parentWidth - childWidth) / 2 + (parentWidth - childWidth) / 2,
        Alignment.y * (parentHeight - childHeight) / 2 + (parentHeight - childHeight) / 2)

Among them, childWidth is the width of the child element, and childHeight is the height of the child element.

Now let's look at the above example again. We bring Alignment(1.0, -1.0) into the above formula, and the actual offset coordinate of FlutterLogo is (60, 0).

3. FractionalOffset

FractionalOffsetInherited from Alignment, the only difference between it and Alignment is that the coordinate origin is different! FractionalOffsetThe origin of the coordinates is the left vertex of the rectangle, which is consistent with the layout system, so it will be easier to understand. The coordinate conversion formula of FractionalOffset is:

实际偏移 = (FractionalOffse.x * (parentWidth - childWidth), FractionalOffse.y * (parentHeight - childHeight))

4. Comparison between Align and Stack

As you can see, both Align and Stack/Positioned can be used to specify the offset of the child element relative to the parent element, but they still have two main differences:

  • The positioning reference system is different; Stack/Positionedthe positioning reference system can be the four vertices of the parent container rectangle; while Align needs to determine the origin of the coordinates through the alignment parameter first, different alignments will correspond to different origins, and the final offset needs to be converted through alignment formula to calculate.
  • StackThere can be multiple child elements, and the child elements can be stacked, while Align can only have one child element, and there is no stacking.

5. Center component

The Center component is defined as follows:

class Center extends Align {
  const Center({ Key? key, double widthFactor, double heightFactor, Widget? child })
    : super(key: key, widthFactor: widthFactor, heightFactor: heightFactor, child: child);
}

It can be seen that Center inherits from Align, which has only one less alignment parameter than Align; since the alignment value in the Align constructor is Alignment.center, we can think that the Center component is actually determined by the alignment (Alignment.center). Align.

We mentioned above that when the widthFactor or heightFactor is null, the width and height of the component will take up as much space as possible, which requires special attention. Let us illustrate with an example:

Column(
      children: [
        DecoratedBox(
          decoration: BoxDecoration(color: Colors.red),
          child: Center(
            child: Text("xxx"),
          ),
        ),
        DecoratedBox(
          decoration: BoxDecoration(color: Colors.red),
          child: Center(
            widthFactor: 1,
            heightFactor: 1,
            child: Text("xxx"),
          ),
        )
      ],
    );

insert image description here

八、LayoutBuilder、AfterLayout

1. LayoutBuilder

Through LayoutBuilder, we can get the constraint information passed by the parent component during the layout process, and then we can dynamically build different layouts according to the constraint information.

For example, we implement a responsive Column component, ResponsiveColumn, whose function is to display subcomponents as one column when the currently available width is less than 200, otherwise display as two columns. Simply implement it:

class _LayoutBuilderTestPageState extends State<LayoutBuilderTestPage> {
  @override
  Widget build(BuildContext context) {
    var _children = List.filled(6, Text("A"));

    return Scaffold(
      appBar: AppBar(
        title: Text("LayoutBuilder、AfterLayout"),
      ),
      body: Center(
        child: Column(
          children: [
            ResponsiveColumn(children: _children,),
            LayoutLogPrint(child:Text("xx")) // 下面介绍
          ],
        ),
      ),
    );
  }
}

class ResponsiveColumn extends StatelessWidget {
  const ResponsiveColumn({Key? key, required this.children}) : super(key: key);

  final List<Widget> children;

  @override
  Widget build(BuildContext context) {
    // 通过 LayoutBuilder 拿到父组件传递的约束,然后判断 maxWidth 是否小于200
    return LayoutBuilder(
      builder: (BuildContext context, BoxConstraints constraints) {
        if (constraints.maxWidth < 200) {
          // 最大宽度小于200,显示单列
          return Column(children: children, mainAxisSize: MainAxisSize.min);
        } else {
          // 大于200,显示双列
          var _children = <Widget>[];
          for (var i = 0; i < children.length; i += 2) {
            if (i + 1 < children.length) {
              _children.add(Row(
                children: [children[i], children[i + 1]],
                mainAxisSize: MainAxisSize.min,
              ));
            } else {
              _children.add(children[i]);
            }
          }
          return Column(children: _children, mainAxisSize: MainAxisSize.min);
        }
      },
    );
  }
}

It can be found that the use of LayoutBuilder is very simple, but don't underestimate it, because it is very practical and important. It has two main usage scenarios:

  1. You can use LayoutBuilder to achieve responsive layout according to the size of the device.
  2. LayoutBuilder can help us troubleshoot problems efficiently. For example, LayoutBuilder is very useful when we encounter layout problems or want to debug the constraints of a certain node layout in the component tree.

Constraint information when printing layout
In order to facilitate troubleshooting, we encapsulate a component that can print the constraints passed by the parent component to the child component:

class LayoutLogPrint<T> extends StatelessWidget {
  const LayoutLogPrint({
    Key? key,
    this.tag,
    required this.child,
  }) : super(key: key);

  final Widget child;
  final T? tag; //指定日志tag

  @override
  Widget build(BuildContext context) {
    return LayoutBuilder(builder: (_, constraints) {
      // assert在编译release版本时会被去除
      assert(() {
        print('${tag ?? key ?? child}: $constraints');
        return true;
      }());
      return child;
    });
  }
}

In this way, we can use constraint information anywhere in the LayoutLogPrint component tree, such as:

LayoutLogPrint(child:Text("xx"))

Console output:

flutter: Text("xx"): BoxConstraints(0.0<=w<=428.0, 0.0<=h<=823.0)

It can be seen that the maximum width of the display space of Text("xx") is 428, and the maximum height is 823.

2. Flutter build and layout

By observing the example of LayoutBuilder, we can also find a conclusion about Flutter build (build) and layout (layout): Flutter
's build and layout can be executed interleaved, not strictly in the order of build first and then layout. For example, in the above example, the LayoutBuilder component is encountered during the build process, and the builder of LayoutBuilder is executed in the layout phase (only in the layout phase can the constraint information of the layout process be obtained). After creating a new widget in the builder, the Flutter framework then The build method of the widget will be called, and it enters the build phase.

Reference content:
Flutter in action: the second edition
of Flutter Chinese documentation

Guess you like

Origin blog.csdn.net/guoxulieying/article/details/131528235