This is the ninth day of my participation in the 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

👉 In-depth understanding of Flutter layout constraints

From the previous article, we learned about the division of labor between Widget, Element, and RenderObject, where RenderObject is responsible for layout measurement.

Let’s take a look at the layout process. There are three main problems to be solved here: 1. Understand RenderObject in depth. 2

Understand RenderObject in depth

In the previous three tree articles, we learned the concept of RenderObject. In order to better understand the layout drawing process, let’s take a closer look at RenderObject. After all, RenderObject is the carrier of the entire layout.

There is a lot of space in the official documentation to introduce it, so let me look at it first.

From the description of the document, we can know the following:

RenderObject is the heart of rendering

RenderObject implements the layout and drawing protocols, but subclasses calculate how to lay them out.

The RenderObject holds the parent node, which holds the data that the parent cares about.

Here we go:

Here are some things we can know from above:

If you want to customize, in most cases, you inherit from RenderBox

In addition to defining the meaning, the document also describes the layout protocol.

Here are some things we can know from above:

Layout is input + output. RenderBox inputs box constraints, outputs dimensions.

** The child layout is performLayout

Constraints can be passed from top to bottom along the render tree. The root of the Flutter APP is the RenderView. The layout process is just two methods: performLayout and Layout.

summary

Above we introduced render objects, know the basic information of render objects. RenderObject is the core of rendering. It defines the basic layout protocol. RenderBox implements the box constraints that we are familiar with. And the constraints are passed from the top down to the Layout method. There are two main methods to implement a layout: performLayout and Layout methods.

Where did you start the layout

PerformLayout and Layout are two methods: performLayout and Layout methods.

The relationship looks like this, in a render object. The performLayout method calls the layout method of the child node, which then calls the performLayout method of the child node.

Something like this:

The root node of a Flutter is RenderView, so the start of the layout is RenderView’s performLayout method.

RenderVie W’s parent initiates the layout of RenderView, but RenderView has no parent. Who initiates the layout?

This is what roots are for, when a Flutter creates the root nodes of three trees. The following code is called (RenderView), which we’ll talk about later when we talk about binding:

One of them, markNeedsLayout, is the process that initiates the layout.

Let’s look at RenderView’s performLayout method:

@override
void performLayout() {
  _size = configuration.size;
  if(child ! =null) child! .layout(BoxConstraints.tight(_size)); }Copy the code

Did two very important things:

The size is determined. This size is the width and height of the window, usually the width and height of the FlutterView

Initiate the layout flow of the child nodes, and the constraints are tight, forcing the child nodes to be window widths

summary

The starting point of the layout is The performLayout of RenderView. In the performLayout, the size of the whole Flutter App is determined and the layout process of descendant nodes is initiated.

Next, let’s look at the complex layout process.

How is it laid out

We know that the layout function is: according to the input explicit output, input is the box constraint, output is the size.

And the layout process starts with the Layout method, calling the performLayout method internally. Now let’s look at how these two methods determine the inputs and outputs.

A Layout method that defines a concept

Layout description

As always, let’s look at the description of the method

Compute the layout for this render object. Implement layout for render object This method is the main entry point for parents to ask their children to update their layout information. The parent passes a constraints object, which informs the child as to which layouts are permissible. The child is required to obey the given constraints. Parent uses this method to get the child nodes to update the layout information, and parent passes a constraint object that the child needs to follow. If the parent reads information computed during the child's layout, the parent must pass true for `parentUsesSize`. In that case, the parent will be marked as needing layout whenever the child is marked as needing layout because the parent's layout information depends on the child's layout information. If the parent uses the default value (false) for `parentUsesSize`, the child can change its layout information (subject to the given constraints) without informing the parent. ParentUsesSize indicates whether the parent Widget depends on the layout information of the child. The default is false. If parentUsesSize is true, the parent depends on the child. So when the child is marked for a layout (markNeedsLayout, then the parent is marked for a layout as well. If false (parents do not depend on children), the children do not need to notify the parent when they want to be rearranged. The boundary of the layout is itself. Subclasses should not override [layout] directly. Instead, they should override [performResize] and/or [performLayout]. The [layout] method delegates the actual work to [performResize] and [performLayout]. Subclasses are better off not overwriting layout methods directly. Instead, override the performResize and performLayout methods. The parent's [performLayout] method should call the [layout] of all its children unconditionally. It is the [layout] method's responsibility (as implemented here) to return early if the child does not need to do any work to update its layout information. The parent node's performLayout method calls the Layout method for each byte point. Layout returns layout information as early as possible.Copy the code

As described above, we know the following:

The Layout method performs the layout process

To execute the layout process, you need two parameters: constraint and parentUsesSize

The specific layout calculation should be in performResize and performLayout, somewhat similar to the Android onLayout and Layout methods

Layout of the boundary

Before we talk about layout, let’s look at one concept: the boundaries of a layout

When we lay out node A, if the parent node of node A depends on the layout information of node A, then the parent node of node A is the upper bound of node A. When node A is marked as needing layout, the upper bound nodes of node A will also be marked as needing layout.

What is the search process? The RenderObject that we introduced earlier holds a reference to the parent node. The parent property.

So what’s the criterion for determining whether it’s upper bound? Whether the parent node depends on the child node. (This sentence seems to be nonsense, listen to your words, such as listen to words)

The render node of a Flutter is superlayer as long as the boundary is not itself. Boundaries are their own:

The parentUsesSize parameter is false

The size of the node only depends on itself, sizedByParent is true, and the constraint type is constraints. IsTight

The node is the root node

Except for those three cases, the remaining boundaries are all upper layers

Layout Process

After introducing the front foreshadowing, let’s look at the specific layout process. The entire process is less than 50 lines of code.

The whole process can be divided into two parts: finding the layout upper bound and initiating the layout drawing

Let’s look at the first part

if(! parentUsesSize || sizedByParent || constraints.isTight || parentis! RenderObject) {
  relayoutBoundary = this;
} else {
  relayoutBoundary = (parent! as RenderObject)._relayoutBoundary;
}
Copy the code

If parentUsesSize is false, then the drawing upper bound is itself. If sizedByParent is true, draw the boundary itself. If the constraint is compact, constraints. IsTight. For box constraints the width and height are fixed. The parent node is either the render node or the root node.

Else is not one of these four, the boundary is the parent boundary

SizeByParent means that its size is determined by the parent node. This value is generally false, and only a few special nodes are true.

For example: we are familiar with Offstage component, Offstage property is bool, Offstage is true, Offstage is not displayed, draw boundary is itself.

There is also the RenderViewport node, whose size is determined by the parent layout, such as the ListView, which is 200 x 300

Read on:

if (! _needsLayout && constraints == _constraints && relayoutBoundary == _relayoutBoundary) { return; }Copy the code

If the old and new information is the same, the layout is not needed.

Nodes are not marked as “dirty” constraints are the same as boundaries are the same

Look further down:

_constraints = constraints;
if(_relayoutBoundary ! =null&& relayoutBoundary ! = _relayoutBoundary) { visitChildren(_cleanChildRelayoutBoundary); } _relayoutBoundary = relayoutBoundary;Copy the code

If you need a layout, you need a layout.

  • Save constraint Informationconstraints
  • Clear the upper bound information of the empty child node
  • Save the upper boundrelayoutBoundaryinformation

The search for the upper bound of the layout has been completed.

Let’s look at part two

The first process judgment point: is it sizedByParent

SizedByParent is a property, and the default is false. Only the following types of nodes are true; all others are false

if (sizedByParent) {
  try {
    performResize();
  } catch (e, stack) {
  }
}
Copy the code

If the width and height are determined by the parent node, the Size can be determined directly

Such as:

OffstageWhen no child components are displayed, size is the minimum constraint 0*0

The size of the Viewport is the maximum size of the parent layout. The Viewport is the render node that hosts the Listview, so we can’t put the Listview in a container with an undefined width and height.

Now let’s look at the final layout

try {
  performLayout();
  markNeedsSemanticsUpdate();
} catch (e, stack) {
}
_needsLayout = false;
markNeedsPaint();
Copy the code

That’s the performLayout method, which is the specific layout.

For example, we know LayoutBuilder. It determines its width and height after child layout in the performLayout stage. And its layout is the layout of the child nodes.

After the layout the identity bit is set to false and initiates a drawing process. This is the complete layout flow.

summary

We combed through the entire layout protocol, from the determination of layout boundaries to the initiation of drawing. Most of these processes are uniform, and each subclass only needs to implement the performLayout method.

Implement your own layout behavior in a method, such as how big it is, whether it has moved, etc.

conclusion

Layout is the rendering core of Flutter. The macro process of Flutter is centered in the Layout method of RenderObject, which deals with the relationship between inputs (constraints) and outputs (size).

Child nodes can implement their own layout behavior in the performLayout method. This one is more theoretical, and the next one will analyze the layout behavior in different scenarios according to specific examples.