This paper mainly introduces Row and Column controls in Flutter layout, introduces their layout behavior and usage scenarios in detail, and analyzes the source code.

1. Row

A widget that displays its children in a horizontal array.

1.1 introduction

A multichild control common in Flutter that arranges children in a row. Presumably borrowed from the Flex layout on the Web, so many properties and representations are similar. Note, however, that it does not have a scrolling attribute, and if it exceeds a line, an overflow message will be displayed under DEBUG.

1.2 Layout Behavior

Row layout has six steps, and this layout representation comes from Flex (the parent of Row and Column) :

  1. First lay out the child with flex null or 0 according to the unrestricted main axis constraint, then adjust the child according to the cross axis constraint.
  2. According to the non-empty Flex value, divide the remaining space along the main axis into corresponding equal parts;
  3. For the child whose flex value is not empty in the above step, adjust it in the direction of the cross axis and use the maximum constraint in the direction of the main axis to fill the space allocated in Step 2.
  4. The Flex cross axis range is taken from the maximum cross axis of the child node;
  5. The value of the main Flex is determined by the mainAxisSize property, which can take Max, min, and a specific value;
  6. The position of each child is determined by the mainAxisAlignment and crossAxisAlignment.

The Row layout behavior is simple enough to follow in the footsteps of Flex layouts on the Web, including the concepts of spindles, cross axes, and so on.

1.3 Inheritance Relationship

Object > Diagnosticable > DiagnosticableTree > Widget > RenderObjectWidget > MultiChildRenderObjectWidget > Flex > Row
Copy the code

Row and Column are Flex subclasses, and they are implemented by Flex with different parameters.

1.4 Sample Code

Row(children: <Widget>[Expanded(child: Container(color: Colors. Red, padding: EdgeInsets. All (5.0)), flex: 1,), Expanded(child: Container(color: color.yellow, padding: EdgeInsets. All (5.0), flex: 2,), Expanded(child: Container(color: color.blue, padding: EdgeInsets. All (5.0), flex: 1,),],)Copy the code

A very simple example is to use Expanded control to divide the width of a row into four equal sections, with the first and third children occupying 1/4 of the area and the second child occupying 1/2 of the area, controlled by the Flex property.

1.5 Source Code Parsing

Constructors are as follows:

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

1.5.1 Attribute Resolution

MainAxisAlignment: The alignment in the main axis direction that takes effect on the position of the child. Default is Start.

MainAxisAlignment enumeration value:

  • Center: Place children in the center of the spindle;
  • End: place children at the end of the spindle;
  • SpaceAround: Divide the blank area along the main axis so that the blank area between children is equal, but the blank area between the beginning and the end of the child is 1/2;
  • SpaceBetween: divide the blank area along the main axis evenly, so that the blank area between children is equal, and the beginning and the end of the child are close to the beginning and the end, without gap;
  • Spaceinstituted: evenly dividing the blank areas along the main axis so that the blank areas between children are equal, including beginning and end children;
  • Start: Place children at the starting point of the spindle;

What distinguishes spaceAround, spaceBetween, and spaceinstituted is how the beginning and end child are treated. Its distance from the beginning and end is 1/2 of the blank area, 0 and 1 respectively.

MainAxisSize: The value of space occupied in the main axis, Max by default.

There are two values for MainAxisSize:

  • Max: Maximizes the available space in the main axis based on the layout constraints passed in;
  • Min: The opposite of Max, minimizes the available space in the main axis;

CrossAxisAlignment: The alignment of children in the cross axis direction is slightly different from the MainAxisAlignment.

CrossAxisAlignment enumeration values are as follows:

  • Baseline: aligns children’s baseline in the intersecting axes;
  • Center: Children are shown in the center of the cross axis;
  • End: Children are shown at the end of the cross axis;
  • Start: Children are displayed at the starting point on the cross axis;
  • Stretch: let children fill the cross axis direction;

TextDirection: Arabic compatible Settings, generally no need to handle.

VerticalDirection: Defines the order in which children are placed. The default is down.

There are two kinds of VerticalDirection enumerated values:

  • Down: layout from top to bottom;
  • Up: Layout from bottom to top.

Top corresponds to Row and Column, left and top, and bottom to right and bottom.

TextBaseline: There are two ways to use TextBaseline, which have been described previously.

1.5.2 source

The source code for Row and Column is a single constructor, all implemented in their parent Flex class.

Constructors for Flex

Flex({
  Key key,
  @required this.direction,
  this.mainAxisAlignment = MainAxisAlignment.start,
  this.mainAxisSize = MainAxisSize.max,
  this.crossAxisAlignment = CrossAxisAlignment.center,
  this.textDirection,
  this.verticalDirection = VerticalDirection.down,
  this.textBaseline,
  List<Widget> children = const <Widget>[],
})
Copy the code

As you can see, the Flex constructor takes one more parameter than Row and Column. The difference between Row and Column is the difference between this direction parameter. Row when Axis. Horizontal, Column when Axis. Vertical.

Let’s take a look at Flex’s layout functions. Since there are many layout functions, we break them down into sections:

while(child ! = null) { final FlexParentData childParentData = child.parentData; totalChildren++; final int flex = _getFlex(child);if (flex > 0) {
    totalFlex += childParentData.flex;
    lastFlexChild = child;
  } else {
    BoxConstraints innerConstraints;
    if (crossAxisAlignment == CrossAxisAlignment.stretch) {
      switch (_direction) {
        case Axis.horizontal:
          innerConstraints = new BoxConstraints(minHeight: constraints.maxHeight,
                                                maxHeight: constraints.maxHeight);
          break;
        case Axis.vertical:
          innerConstraints = new BoxConstraints(minWidth: constraints.maxWidth,
                                                maxWidth: constraints.maxWidth);
          break; }}else {
      switch (_direction) {
        case Axis.horizontal:
          innerConstraints = new BoxConstraints(maxHeight: constraints.maxHeight);
          break;
        case Axis.vertical:
          innerConstraints = new BoxConstraints(maxWidth: constraints.maxWidth);
          break;
      }
    }
    child.layout(innerConstraints, parentUsesSize: true);
    allocatedSize += _getMainSize(child);
    crossSize = math.max(crossSize, _getCrossSize(child));
  }
  child = childParentData.nextSibling;
}
Copy the code

In the above code, I have removed some assert and error messages in the middle, so as not to affect the actual understanding.

At the beginning of the layout, the child is traversed. Traversal serves two purposes:

  • For any child with a Flex value, calculate the sum of flex and find the last child that contains the flex value. I found this child because the spindle alignment may adjust its position, so I need to find it;
  • For a child that does not contain Flex, adjust the child based on the cross axis orientation Settings.
Final double freeSpace = math.max(0.0, (canFlex? MaxMainSize: 0.0) -allocatedSize);if(totalFlex > 0 || crossAxisAlignment == CrossAxisAlignment.baseline) { final double spacePerFlex = canFlex && totalFlex > 0? (freeSpace / totalFlex) : double.nan; child = firstChild;while(child ! = null) { final int flex = _getFlex(child);if (flex > 0) {
      final double maxChildExtent = canFlex ? (child == lastFlexChild ? (freeSpace - allocatedFlexSpace) : spacePerFlex * flex) : double.infinity;
      double minChildExtent;
      switch (_getFit(child)) {
        case FlexFit.tight:
          assert(maxChildExtent < double.infinity);
          minChildExtent = maxChildExtent;
          break;
        caseFlexFit. Loose: minChildExtent = 0.0;break;
      }
      BoxConstraints innerConstraints;
      if (crossAxisAlignment == CrossAxisAlignment.stretch) {
        switch (_direction) {
          case Axis.horizontal:
            innerConstraints = new BoxConstraints(minWidth: minChildExtent,
                                                  maxWidth: maxChildExtent,
                                                  minHeight: constraints.maxHeight,
                                                  maxHeight: constraints.maxHeight);
            break;
          case Axis.vertical:
            innerConstraints = new BoxConstraints(minWidth: constraints.maxWidth,
                                                  maxWidth: constraints.maxWidth,
                                                  minHeight: minChildExtent,
                                                  maxHeight: maxChildExtent);
            break; }}else {
        switch (_direction) {
          case Axis.horizontal:
            innerConstraints = new BoxConstraints(minWidth: minChildExtent,
                                                  maxWidth: maxChildExtent,
                                                  maxHeight: constraints.maxHeight);
            break;
          case Axis.vertical:
            innerConstraints = new BoxConstraints(maxWidth: constraints.maxWidth,
                                                  minHeight: minChildExtent,
                                                  maxHeight: maxChildExtent);
            break;
        }
      }
      child.layout(innerConstraints, parentUsesSize: true);
      final double childSize = _getMainSize(child);
      allocatedSize += childSize;
      allocatedFlexSpace += maxChildExtent;
      crossSize = math.max(crossSize, _getCrossSize(child));
    }
    if (crossAxisAlignment == CrossAxisAlignment.baseline) {
      final double distance = child.getDistanceToBaseline(textBaseline, onlyReal: true);
      if (distance != null)
        maxBaselineDistance = math.max(maxBaselineDistance, distance);
    }
    final FlexParentData childParentData = child.parentData;
    child = childParentData.nextSibling;
  }
}
Copy the code

The above snippet also does two things:

  • Allocate the remaining space for the child that contains Flex

The size of each Flex is calculated as follows:

Final double freeSpace = math.max(0.0, (canFlex? MaxMainSize: 0.0) -allocatedSize); final double spacePerFlex = canFlex && totalFlex > 0 ? (freeSpace / totalFlex) : double.nan;Copy the code

Where allocatedSize does not include the space occupied by Flex. When the space occupied by each flex is calculated, the child containing flex is adjusted according to the Settings of the cross axis.

  • Calculate the baseline

If the alignment of the cross axes is the baseline, the maximum baseline is calculated as the overall baseline.

switch (_mainAxisAlignment) {
  caseMainAxisAlignment. Start: leadingSpace = 0.0; BetweenSpace = 0.0;break;
  case MainAxisAlignment.end:
    leadingSpace = remainingSpace;
    betweenSpace = 0.0;
    break;
  caseMainAxisAlignment. Center: leadingSpace = remainingSpace / 2.0; BetweenSpace = 0.0;break;
  caseMainAxisAlignment. SpaceBetween: leadingSpace = 0.0; betweenSpace = totalChildren > 1 ? Totalingspace/(Totalchildren-1) : totalchildren-1;break;
  caseMainAxisAlignment.spaceAround: betweenSpace = totalChildren > 0 ? RemainingSpace/totalChildren: 0.0; LeadingSpace = betweenSpace / 2.0;break;
  caseMainAxisAlignment.spaceEvenly: betweenSpace = totalChildren > 0 ? Totalingspace/(totalChildren + 1) : remainingSpace/(totalChildren + 1) : remainingSpace; leadingSpace = betweenSpace;break;
}
Copy the code

Then, position the child in the desired alignment on the main axis. The code above is just a calculation of the front and back blank space values, and you can see the difference between spaceBetween, spaceAround, and spaceinstituted.

double childMainPosition = flipMainAxis ? actualSize - leadingSpace : leadingSpace;
child = firstChild;
while(child ! = null) { final FlexParentData childParentData = child.parentData; double childCrossPosition; switch (_crossAxisAlignment) {case CrossAxisAlignment.start:
    caseCrossAxisAlignment.end: childCrossPosition = _startIsTopLeft(flipAxis(direction), textDirection, verticalDirection) == (_crossAxisAlignment == CrossAxisAlignment.start) ? 0.0: crossSize - _getCrossSize(child);break;
    caseCrossAxisAlignment. Center: childCrossPosition = 2.0 _getCrossSize crossSize/(child) / 2.0;break;
    caseCrossAxisAlignment. Stretch: childCrossPosition = 0.0;break;
    caseCrossAxisAlignment. Baseline: childCrossPosition = 0.0;if(_direction == Axis.horizontal) { assert(textBaseline ! = null); final double distance = child.getDistanceToBaseline(textBaseline, onlyReal:true);
        if(distance ! = null) childCrossPosition = maxBaselineDistance - distance; }break;
  }
  if (flipMainAxis)
    childMainPosition -= _getMainSize(child);
  switch (_direction) {
    case Axis.horizontal:
      childParentData.offset = new Offset(childMainPosition, childCrossPosition);
      break;
    case Axis.vertical:
      childParentData.offset = new Offset(childCrossPosition, childMainPosition);
      break;
  }
  if (flipMainAxis) {
    childMainPosition -= betweenSpace;
  } else {
    childMainPosition += _getMainSize(child) + betweenSpace;
  }
  child = childParentData.nextSibling;
}
Copy the code

Finally, adjust the position of the child according to the alignment of the cross axes, and the layout is finished.

We can follow the overall process:

  • Calculate the sum of Flex and find the last child with Flex set;
  • For the child without Flex, adjust the alignment according to the cross axis alignment, and calculate the size of the region in the main axis;
  • Calculate the space occupied by each flex and adjust the child containing flex according to the cross axis alignment;
  • If the cross axis is set to baseline alignment, the overall baseline value is calculated;
  • Adjust the child according to the spindle alignment;
  • Finally, adjust all child positions according to the cross axis alignment to complete the layout.

1.6 Application Scenarios

Row and Column are very common layout controls. This can be used in general situations, such as when controls need to be displayed in a row or column. This doesn’t mean that you can only use Row or Column for layout. You can also use Stack, depending on the scenario.

2. Column

When I explain Row, I follow Flex’s layout behavior, including source code analysis, which is also done in Flex. Row and Column are both Flex subclasses with different direction parameters. Column is the same as Row in every respect, so I won’t explain it here.

When I explained Flex, I also said that I refer to the Flex layout of the Web. If you have relevant development experience, you can refer to it, so that you can easily understand their usage and principle.

3. The latter

The author has set up a project about Flutter learning. The Github address contains some articles about Flutter learning written by the author. They will be updated regularly and some learning demos will also be uploaded.

4. Reference

  1. Row class
  2. Column class
  3. MainAxisAlignment enum
  4. CrossAxisAlignment enum
  5. MainAxisSize enum
  6. VerticalDirection enum