This is the 10th day of my participation in the First Challenge 2022, for more details: First Challenge 2022

Past wonderful

👉 Flutter will Know will Series – What exactly are Three Trees

👉 Flutter must know must know series — Update reuse mechanism of Element

👉 Flutter must know must series — Element update combat

👉 Flutter must know must Know series – Render tree layout

👉 In-depth understanding of Flutter layout constraints

In previous articles, we learned about the division of widgets, Elements, and RenderObjects, as well as the theory of RenderObject layout. In particular 👉 has an in-depth understanding of the constraints of the Flutter layout: pass down the constraint 👇. Pass the dimensions up to 👆

Theory should be extracted from objective reality and fully proved in objective reality. In this article we will verify 👉’s in-depth understanding of two small examples of Flutter layout constraints from a code perspective.


Case analysis

Case one: Minimum or maximum

The case code is an in-depth understanding of the constraints on the Flutter layout


void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    returnRedContainer(); }}class RedContainer extends StatelessWidget {
  const RedContainer({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    returnContainer( color: Colors.red, ); }}Copy the code

We return the Container directly on the page with a red background. Width and height are not set. You already know that the whole screen is going to be red instead of nothing. The reasons are:

The screen is the parent node of the Container. The screen forces the Container to be the same size as Screen. Therefore, the Container fills the screen with red.

Let’s look at the whole process from a code point of view.

We display RedContainer directly on the screen. The direct children of the root node of the Flutter are RedContainer, so the box constraints of the RedContainer are:

const BoxConstraints({
  this.minWidth = screen width,this.maxWidth = screen width,this.minheight = screen height,this.maxheight = screen height,});Copy the code

Container(color: color.red,) but ColoredBox is constructed:

The constructed nodes are as follows:

ColoredBox has a red background, LimitedBox and ConstrainedBox have layout width and height.

Constraints are passed through layers, and we follow them from ColoredBox to ConstrainedBox.

Rendercoloredbox = renderColoredBox

@override
void performLayout() {
  if(child ! =null) { child! .layout(constraints, parentUsesSize:true); size = child! .size; }else{ size = computeSizeForNoChild(constraints); }}Copy the code

We see that ColoredBox does not participate in the layout process, and the width and height of the red area is the width and height of the child LimitedBox.

The layout process of the child node is called directly during the layout, and the constraints are passed through from the parent node.

In this case, the constraints of the input parameter are the page constraints on the screen width

MinWidth: the screen width maxWidth: constraints. HasBoundedWidth is true, so also is the screen width minHeight: is highly maxHeight: screen and maximum width is similar, is highly/screen

Below we are chasing LimitedBox layout process. Limitedboxes are constructed as follows:


if (child == null && (constraints == null| |! constraints.isTight)) { current = LimitedBox( maxWidth:0.0,
    maxHeight: 0.0,
    child: ConstrainedBox(constraints: const BoxConstraints.expand()),
  );
}

Copy the code

LimitedBox has a maximum width and a maximum height of 0, and its child is ConstrainedBox.

LimitedBox Creates a RenderBox: RenderLimitedBox

Let’s look at the Layout process for RenderLimitedBox:

@override
void performLayout() {
  size = _computeSize(
    constraints: constraints,
    layoutChild: ChildLayoutHelper.layoutChild,
  );
}

Size _computeSize({required BoxConstraints constraints, required ChildLayouter layoutChild }) {
  if(child ! =null) {
    finalSize childSize = layoutChild(child! , _limitConstraints(constraints));return constraints.constrain(childSize);
  }
  return _limitConstraints(constraints).constrain(Size.zero);
}

Copy the code

We see that the size is determined by _computeSize, and that the constraints for the _computeSize method are the constraints passed in by ColoredBox ———— screen width. In the actual _computeSize method, we go to child! = NULL judgment.

Although the Child of the Container is null, the LimitedBox constructed has child nodes. Here we see that it evaluates the original constraints (_limitConstraints). And the result of the calculation as a constraint on the child node.

The point is to calculate:

BoxConstraints _limitConstraints(BoxConstraints constraints) {
  return BoxConstraints(
    minWidth: constraints.minWidth,
    maxWidth: constraints.hasBoundedWidth ? constraints.maxWidth : constraints.constrainWidth(maxWidth),
    minHeight: constraints.minHeight,
    maxHeight: constraints.hasBoundedHeight ? constraints.maxHeight : constraints.constrainHeight(maxHeight),
  );
}
Copy the code

The input parameter constraints, remember who it is, is the ColoredBox constraint — the screen width

Generated values are as follows: minWidth: is the screen width maxWidth: constraints. HasBoundedWidth is true, so also is the screen width minHeight: is highly maxHeight: screen and maximum width is similar, is highly/screen

In other words, the resulting child node constraint is the screen width

Similarly, let’s look at layout measurements in RenderConstrainedBox.

@override
void performLayout() {
  final BoxConstraints constraints = this.constraints;
  if(child ! =null) { child! .layout(_additionalConstraints.enforce(constraints), parentUsesSize:true); size = child! .size; }else{ size = _additionalConstraints.enforce(constraints).constrain(Size.zero); }}Copy the code

Again, the constraints here are the constraints passed in by the parent ———— screen width. RenderConstrainedBox has no child nodes under it. So Size is _additionalConstraints. Enforce (constraints) constrain (Size. Zero).

Who is _additionalConstraints? This is passed by build in the Container.

_additionalConstraints is as follows:

const BoxConstraints({
  this.minWidth = double.infinity,
  this.maxWidth = double.infinity,
  this.minHeight = double.infinity,
  this.maxHeight = double.infinity,
});
Copy the code

Enforce calculation process is as follows:

BoxConstraints enforce(BoxConstraints constraints) {
  return BoxConstraints(
    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

Clamp method you may not be familiar with clamp method, but clamp is a mathematical method that values values within a reasonable range, either by themselves or at both ends.

If the value is between the two ends, the result of clamp is the value itself. If it’s less than the left breakpoint, it’s the left endpoint, if it’s greater than the right endpoint, it’s the right endpoint. This is the clip calculation.

So enforce results:

const BoxConstraints({
  this.minWidth = screen width,this.maxWidth = screen width,this.minheight = screen height,this.maxheight = screen height,});Copy the code

We’re in Constrain counting:

Size constrain(Size size) {
  Size result = Size(constrainWidth(size.width), constrainHeight(size.height));
  return result;
}

double constrainWidth([ double width = double.infinity ]) {
  return width.clamp(minWidth, maxWidth);
}
Copy the code

Clamp width is the screen width, clamp(0, infinite) height is the screen height, clamp(0, infinite)

This is the size, screen width. And upload the dimensions layer by layer. So the whole screen shows a red screen

The whole process is verified: constraints pass down, dimensions pass up.

Case 2: Can I display the specified size

Let’s make a change now: specify the width and height.

class RedContainer extends StatelessWidget {
  const RedContainer({Key? key}) : super(key: key);

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

The entire screen is also displayed, because the parent node forces the entire screen, so let’s look at the force process.

Let’s look at the Container constructor:

Container({
  Key? key,
  this.alignment,
  this.padding,
  this.color,
  this.decoration,
  this.foregroundDecoration,
  double? width,
  double? height,
  BoxConstraints? constraints,
  this.margin,
  this.transform,
  this.transformAlignment,
  this.child,
  this.clipBehavior = Clip.none, }) : constraints = (width ! =null|| height ! =null)
        ? constraints?.tighten(width: width, height: height)
          ?? BoxConstraints.tightFor(width: width, height: height)
        : constraints,
     super(key: key);
     
Copy the code

Since we set width and height, we will have tightFor Constraints in the constructor. The result of the construction is:

const BoxConstraints({
  this.minWidth = 100.this.maxWidth = 100.this.minHeight = 100.this.maxHeight = 100});Copy the code

Based on the logic of the build method, the following nodes are constructed

ColorBox, ConstrainedBox

Let’s look at the layout logic of the corresponding RenderConstrainedBox.

@override
void performLayout() {
  final BoxConstraints constraints = this.constraints;
  if(child ! =null) { child! .layout(_additionalConstraints.enforce(constraints), parentUsesSize:true); size = child! .size; }else{ size = _additionalConstraints.enforce(constraints).constrain(Size.zero); }}Copy the code

_additionalConstraints is 100 * 100. Constraints is the screen width constraint passed by the parent node. Enforce results are still screen width and height.

BoxConstraints enforce(BoxConstraints constraints) {
  return BoxConstraints(
    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

MinWidth indicates the screen width (greater than 100 constraints). MaxWidth indicates the screen width (greater than 100 constraints).

So the minWidth of the result is the screen width, and so on. Therefore, the constraint on the layout ColorBox is the screen width and height.

In case one, the result is still red.

The whole process verifies that the parent node forces the child node to be large. When a widget tells its child that it must be of a certain size, We say the widget supplies tight constraints to its child.

conclusion

Above we through two cases, step by step to track the layout process, know how to track the layout of a component. Conclusion is not important, know how to chase is the most important.

All you need to do is follow these steps:

First: Find the render object through the Widget

Second: find performLayout and performResize for the render object