1. Introduction

The RenderObject’s main responsibilities in Flutter are layout and drawing. Using the Element Tree described in the previous article, the Flutter Framework generates a RenderObject Tree. Its main functions are as follows:

  • The layout,RenderBoxTo start, toRenderObject TreeLayout from top to bottom.
  • Draw, passCanvasObject,RenderObjectYou can draw itself and where it isRenderObject TreeThe child node in.
  • Click test,RenderObjectPass the click event from top to bottom, passing its location andbehaviorTo control whether to respond to a click event.

RenderObject Tree is the underlying layout and rendering system. Most Flutter developers do not need to interact directly with the RenderObject Tree, but instead use widgets, and the RenderObject Tree is built automatically by the Flutter Framework. RenderObject has a parent and a ParentData Slot. A Slot is a reserved interface or location that is accessed or occupied by other objects. This interface or location is usually represented by reserved variables in software. ParentData is a reserved variable, which is assigned by the parent. The parent usually stores data related to the child elements through ParentData of the child RenderObject, such as in the Stack layout, The RenderStack would then store the offset data of the child element in its ParentData (see the realization of space). The principles of ParentData are detailed in the article Entitled “Framework Analysis of the Flutter (VI) – ParentData”, which is available to interested readers.

2. RenderObject classification

As shown in the figure above, RenderObjects fall into four main categories:

  • RenderView

RenderView is the root node of the entire RenderObject Tree, representing the entire output interface.

  • RenderAbstractViewport

RenderAbstractViewport is an interface designed for a RenderObject that displays only part of its content.

  • RenderSliver

RenderSliver is all realized the effect of sliding RenderObject base class, its subclasses are commonly used RenderSliverSingleBoxAdapter, etc.

  • RenderBox

RenderBox is a 2D Cartesian coordinate system of RenderObject base class, general RenderObject is inherited from RenderBox, such as RenderStack, it is also a general custom RenderObject base class.

3. Core process

RenderObject is mainly responsible for layout, drawing, and hit test. The following will explain these core processes respectively.

  • layout

The layout function is layout. This function is used to layout the node and its children using the Constraints and parentUsesSize control parameters passed from the parent node. Constraints is a constraint on the layout of the node. The principle is that Constraints is down, Sizes is up, and the parent sets the location of the node. That is:

  1. aWidgetFrom its parent nodeConstraintsAnd pass it to the child node.
  2. theWidgetLayout the child nodes.
  3. Finally, the node tells its parent itsSizes.

We’ll cover this process in more detail in the next article, but just keep this principle in mind for now.

The parentUsesSize value is true when the layout of this node depends on the layout of its children. In this case, if the child node is marked as needing a layout, this node will also be marked as needing a layout. When the next frame is drawn, both the local and child nodes will be rearranged. On the other hand, if the value of parentUsesSize is false, the parent node does not need to be notified when the child node is rearranged.

RenderObject subclasses should not override RenderObject’s layout function directly. Instead, they should override performResize and performLayout, which are the functions responsible for the actual layout.

RenderObject () ¶ RenderObject ();

void layout(Constraints constraints, { bool parentUsesSize = false{})//1. Determine whether the layout needs to be rearranged according to relayoutBoundary
  RenderObject relayoutBoundary;
  if(! parentUsesSize || sizedByParent || constraints.isTight || parentis! RenderObject) {
    relayoutBoundary = this;
  } else {
    relayoutBoundary = (parent as RenderObject)._relayoutBoundary;
  }
  if(! _needsLayout && constraints == _constraints && relayoutBoundary == _relayoutBoundary) {return;
  }
  _constraints = constraints;
//2. Update relayout boundary on the child node
  if(_relayoutBoundary ! =null&& relayoutBoundary ! = _relayoutBoundary) {// The local relayout boundary has changed, must notify children in case
    // they also need updating. Otherwise, they will be confused about what
    // their actual relayout boundary is later.
    visitChildren(_cleanChildRelayoutBoundary);
  }
  _relayoutBoundary = relayoutBoundary;
//3. Recalculate the size and rearrange the layout
  if (sizedByParent) {
    try {
      performResize();
    } catch (e, stack) {
      _debugReportException('performResize', e, stack); }}try {
    performLayout();
    markNeedsSemanticsUpdate();
  } catch (e, stack) {
    _debugReportException('performLayout', e, stack);
  }
  _needsLayout = false;
  markNeedsPaint();
}
Copy the code

As you can see from the source code, relayoutBoundary is an important parameter in layout function. When the size of a component changes, the size of its parent may also be affected, so the parent node needs to be notified. If you iterate this way, you need to notify the whole RenderObject Tree to rearrange, which will inevitably affect the layout efficiency. Therefore, the RenderObject Tree is segmtioned by relayoutBoundary. If relayoutBoundary is encountered, the parent node will not be notified to rearrange, because its size will not affect the size of the parent node. In this way, you only need to rearrange a section of RenderObject Tree, improving the layout efficiency. RelayoutBoundary will be explained in detail in the following articles. At present, it is only necessary to understand that relayoutBoundary will segment RenderObject Tree to improve the layout efficiency.

  • draw

The corresponding paint function is paint, which is used to draw the RenderObject and its subrenderObject on the Canvas. RenderObject subclasses should override this function and add the drawing logic to it.

RenderFlex: RenderObject: RenderFlex: RenderObject: RenderFlex: RenderFlex

void paint(PaintingContext context, Offset offset) {
//1. No overflow, directly draw
  if(! _hasOverflow) { defaultPaint(context, offset);return;
  }

//2. Empty, do not need to draw
  // There's no point in drawing the children if we're empty.
  if (size.isEmpty)
    return;

//3. According to clipBehavior, determine whether to cut the part that overflows the boundary
  if (clipBehavior == Clip.none) {
    defaultPaint(context, offset);
  } else {
    // We have overflow and the clipBehavior isn't none. Clip it.
    context.pushClipRect(needsCompositing, offset, Offset.zero & size, defaultPaint, clipBehavior: clipBehavior);
  }

//4. Draw an overflow error message
  assert(() {
    // Only set this if it's null to save work. It gets reset to null if the
    // _direction changes.
    final List<DiagnosticsNode> debugOverflowHints = <DiagnosticsNode>[
      ErrorDescription(
        'The overflowing $runtimeType has an orientation of $_direction. '
      ),
      ErrorDescription(
        'The edge of the $runtimeType that is overflowing has been marked '
        'in the rendering with a yellow and black striped pattern. This is '
        'usually caused by the contents being too big for the $runtimeType. '
      ),
      ErrorHint(
        'Consider applying a flex factor (e.g. using an Expanded widget) to '
        'force the children of the $runtimeType to fit within the available '
        'space instead of being sized to their natural size.'
      ),
      ErrorHint(
        'This is considered an error condition because it indicates that there '
        'is content that cannot be seen. If the content is legitimately bigger '
        'than the available space, consider clipping it with a ClipRect widget '
        'before putting it in the flex, or using a scrollable container rather '
        'than a Flex, like a ListView.')];// Simulate a child rect that overflows by the right amount. This child
    // rect is never used for drawing, just for determining the overflow
    // location and amount.
    Rect overflowChildRect;
    switch (_direction) {
      case Axis.horizontal:
        overflowChildRect = Rect.fromLTWH(0.0.0.0, size.width + _overflow, 0.0);
        break;
      case Axis.vertical:
        overflowChildRect = Rect.fromLTWH(0.0.0.0.0.0, size.height + _overflow);
        break;
    }
    paintOverflowIndicator(context, offset, Offset.zero & size, overflowChildRect, overflowHints: debugOverflowHints);
    return true; } ()); }Copy the code

This part of the code logic is: first determine whether overflow, no overflow, call defaultPaint to complete the drawing, then see whether empty, if size is empty, directly return, and finally draw overflow information.

DefaultPaint:

void defaultPaint(PaintingContext context, Offset offset) {
  ChildType child = firstChild;
  while(child ! =null) {
    final ParentDataType childParentData = child.parentData asParentDataType; context.paintChild(child, childParentData.offset + offset); child = childParentData.nextSibling; }}Copy the code

You can see that defaultPaint calls paintChild to draw the children, and if the children have children, paintChild will eventually call its paint and then call defaultPaint, creating a circular recursive call to draw the entire RenderObject Tree.

  • Hit testing

The hitTest is to determine if a component needs to respond to a click event. The entry point is the hitTest function of the RenderView on the root node of the RenderObject Tree. Here is the source code for this function:

bool hitTest(HitTestResult result, { Offset position }) {
  if(child ! =null)
    child.hitTest(BoxHitTestResult.wrap(result), position: position);
  result.add(HitTestEntry(this));
  return true;
}
Copy the code

As you can see from the constructor of the RenderView, child is the RenderBox class, so let’s look at the hitTest function of the RenderBox.

bool hitTest(BoxHitTestResult result, { @required Offset position }) {
  if (_size.contains(position)) {
    if (hitTestChildren(result, position: position) || hitTestSelf(position)) {
      result.add(BoxHitTestEntry(this, position));
      return true; }}return false;
}
Copy the code

The code logic is simple. If the click event is inside the RenderObject, if it is inside and hitTestSelf or hitTestChildren returns true, then the RenderObject has passed the hit test and needs to respond to the event, The clicked RenderObject needs to be added to the BoxHitTestResult list, and the clicked event is no longer passed down. Otherwise, the hit test is considered to have failed and the event continues down. The hitTestSelf function indicates whether the node passes the hit test, and the hitTestChildren function indicates whether the child node passes the hit test.

4. Core functions

There are many core functions of RenderObject, it is difficult to list one by one, in the core process has been explained in detail RenderObject three core functions. To understand what the core functions do, compare RenderObject’s core functions to Android View’s core functions. Here’s a table for comparison.

role Flutter RenderObject Android View
draw paint() draw()/onDraw()
layout performLayout()/layout() measure()/onMeasure(), layout()/onLayout()
Layout constraints Constraints MeasureSpec
Layout Protocol 1 The Constraints parameter of performLayout() indicates the layout restrictions imposed by the parent on the children The two parameters of measure() indicate the layout restrictions of the parent node on the child node
Layout Protocol 2 PerformLayout () should call layout() for each child node OnLayout () should call layout() for each child node
Layout parameters parentData mLayoutParams
Request the layout markNeedsLayout() requestLayout()
Request drawing markNeedsPaint() invalidate()
Add a child adoptChild() addView()
Remove the child dropChild() removeView()
Associated to a window/tree attach() onAttachedToWindow()
Disassociate from window/tree detach() onDetachedFromWindow()
Access to the parent parent getParent()
Touch events hitTest() onTouch()
User input event handleEvent() onKey()
rotation rotate() onConfigurationChanged()

It can be seen that RenderObject and Android View have many functions corresponding to each other, RenderObject is relative to the Android View layout rendering functions separate out, simplifying the logic of the View.

5. Summary

This article mainly introduces the RenderObject related knowledge, focusing on its classification, the core process, and the core function. The highlights are as follows:

  • RenderObjectMainly responsible for drawing, layout, hit test, etc.
  • RenderObjectThe principle of layout is,ConstraintsDown,SizesUp, the parent node sets the position of the local node.
  • RenderViewAs a wholeRenderObject TreeThe root node ofchildIs aRenderBoxThe type ofRenderObject.

6. Refer to the documents

A brief analysis of combat Flutter RenderObject

7. Related articles

Framework Analysis of Flutter (1) — Overview of Flutter Framework Analysis (2) — Widget Framework Analysis (3) — Element Framework Analysis (5) — Widget, Element, Element RenderObject Tree Flutter framework analysis (6) -Constraint Flutter framework Analysis (7) -relayoutBoundary Flutter Framework Analysis (8) -Platform Channel Flutter framework Analysis – Parent Data Flutter framework Analysis -InheritedWidget