Fluent frame analysis Constraint

1. Preface

stay Fluent framework analysis (IV) - RenderObject In this article, we briefly introduced the core rules of RenderObject layout in fluent. Constraint is down, Size is up, and the parent node sets the position of this node. In this article, we will analyze this rule in detail.

2. Analysis of layout principles

The core rules of RenderObject layout are explained as follows:

  1. A Widget gets the Constraint from its parent node and passes it to the child node.
  2. The Widget lays out its child nodes.
  3. Finally, the node tells its parent node its Size.
    The fluent framework performs depth first traversal of the RenderObject Tree. The Constraint is gradually passed down by passing the parent to the child. In order to calculate its own Size, RenderObject must follow the constraints passed down by its parent node. For some renderobjects that depend on the Size of their child nodes, it is necessary to obtain the Size of their child nodes before calculating their Size. Therefore, the Size of RenderObject will be passed up step by step.
    The schematic diagram of the rule is as follows:

Next, we will analyze this core rule through a simple example.

3. Examples

The example code is as follows:

class Example3Test extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Container(width: 100, height: 100, color: Colors.red),
    );
  }
}

As you can see, the code is very simple, that is, draw a red square with length and width of 100 in the middle of the screen. The figure is as follows.

The RenderObject Tree corresponding to this example is as follows:

Next, we will gradually explain the process of Constraint and Size passing in combination with the source code.

The performLayout function of RenderView is as follows:

@override
void performLayout() {
  assert(_rootTransform != null);
//Set its size to the size of the screen
  _size = configuration.size;
  assert(_size.isFinite);

//Set the size of the child node to the size of the screen
  if (child != null)
    child.layout(BoxConstraints.tight(_size));
}

You can see that its Size is the Size of the screen (Size(392.7, 803.6)), and a Constraint(w=392.7, h=803.6) fixed to the screen Size is passed to the child node through the layout function of the child RenderObject, that is, the Size of the child node is forced to be the Size of the screen.

Next, let's look at the performLayout function of the child node RenderPositionedBox. Its source code is as follows:

void performLayout() {
  final BoxConstraints constraints = this.constraints;
  final bool shrinkWrapWidth = _widthFactor != null || constraints.maxWidth == double.infinity;
  final bool shrinkWrapHeight = _heightFactor != null || constraints.maxHeight == double.infinity;

  if (child != null) {
//Pass constraints to child nodes
    child.layout(constraints.loosen(), parentUsesSize: true);
//Use the size of child nodes to calculate their own size
    size = constraints.constrain(Size(shrinkWrapWidth ? child.size.width * (_widthFactor ?? 1.0) : double.infinity,
                                          shrinkWrapHeight ? child.size.height * (_heightFactor ?? 1.0) : double.infinity));
//Set the offset in the parentData of the child node to confirm the position of the child node when drawing
    alignChild();
  } else {
    size = constraints.constrain(Size(shrinkWrapWidth ? 0.0 : double.infinity,
                                          shrinkWrapHeight ? 0.0 : double.infinity));
  }
}

As you can see, firstly, the Constraint(w=392.7, h=803.6) passed from RenderView will be relaxed to constraint (0 < = w < = 392.7, 0 < = h < = 803.6) through the loose function of constraints, and it will be passed to the child node through the layout function of the child node.

The child node calculates its Size in its performLayout function, and then the RenderPositionedBox calculates its own Size according to the Size. The performLayout of child nodes will be analyzed next.

Finally, the position of the child node relative to the RenderPositionedBox is calculated according to the Size of the RenderPositionedBox and the Size of the child node, and the value is given to the offset of the child node parentData.

Next, we analyze the performLayout function of RenderConstrainedBox. Its source code is as follows:

void performLayout() {
  final BoxConstraints constraints = this.constraints;
  if (child != null) {
//Pass constraints to child nodes
    child.layout(_additionalConstraints.enforce(constraints), parentUsesSize: true);
//Use the child node size to calculate its own size
    size = child.size;
  } else {
    size = _additionalConstraints.enforce(constraints).constrain(Size.zero);
  }
}

First, calculate the Constraint required by the child node through the constraints (constraints) passed from the parent node and the constraints (_additionalConstraints) passed from its constructor. In this example, the Constraint is Constraint(w=100, h=100).

Then, since RenderConstrainedBox is only a container containing child nodes, set its own Size to the Size(100, 100) of the child nodes.

There is no need to set the parentdata of the child node Offset, because the child node will be filled with RenderConstrainedBox, so its child node's parentdata Offset is offset(0,0).

Finally, let's look at_ performLayout function of RenderColoredBox.
RenderColoredBox does not override the performLayout function, and its function calling relationship is as follows:

Finally, the performResize function of RenderBox will be called, and its source code is as follows:

void performResize() {
  // default behavior for subclasses that have sizedByParent = true
  size = constraints.smallest;
  assert(size.isFinite);
}

It can be seen that the Constraint passed by the parent node is used to calculate its own Size(100, 100).

Summarizing the above process, the Constraint is passed down by the parent node layer by layer through the layout function in the performLayout function. After the child node calls layout, the child node calculates its Size, and then the parent node calculates its own Size according to the Size of the child node to determine its Size. The flow chart is as follows:

4. Summary

This paper mainly analyzes the core rules of fluent layout in combination with the source code, and its key points are as follows:

  • The core layout rules are Constraint down, Size up, and the parent node sets the position of this node.
  • performLayout generally includes the following steps: first, pass the Constraint to the child node through the layout function. The child node will calculate its own Size in its performLayout function through the layout function, then calculate its own Size through the Size of the child node, and finally calculate the Offset of the parentData of the child node through its own Size and the Size of the child node. This Offset will be used when drawing child nodes.

5. Reference documents

Flutter architectural overview

6. Related articles

Fluent framework analysis (I) – Architecture Overview
Fluent framework analysis (II) -- Widget
Fluent framework analysis (III) -- Element
Fluent framework analysis (IV) - RenderObject
Fluent framework analysis (V) - Widget, Element, RenderObject tree
Fluent framework analysis - Parent Data
Fluent framework analysis InheritedWidget

Keywords: Front-end Android Design Pattern Flutter

Added by tet3828 on Sat, 22 Jan 2022 23:45:33 +0200