Notice: Flutter recently 1.0!

The purpose of this article

  • Analyze the Layout and Paint of flutter
  • What is relayout boundary and repaint boundary
  • How do developers use Relayout Boundary and Repaint Boundary

The directory structure

  • Flutter plotting principle and basic UI flow
  • The role of widgets in flutter drawing
  • Analysis of the Layout
  • Analysis of the Paint
  • conclusion

Flutter plotting principle and basic UI flow

  • Plotting principle of Flutter

  • The basic flow of UI

For example, an input action from the user can be interpreted as a Vsunc signal. In this case, Fliutter will first do Animation work, then Build the current UI, and then the view will start laying out and drawing. Generate view data, but only generate Layer Tree, can not be used directly, still need to Composite into a Layer for Rasterize processing. Generally, there are many levels of flutter, and each layer is directly transferred to GPU, which is very inefficient. Therefore, Composite will be made first to improve efficiency. Only after rasterization will the Flutter-Engine be processed. This is only Framework level work, so no Engine can be seen, and what we analyze is only a small part of the Framework.

The role of widgets in Flutter drawing

Before we do that, we need to understand a few concepts

  • Widget
  • Element
  • RenderObject
Widget

The Widget here is the one we normally write. It is the basic unit of the control implementation of Flutter. A Widget typically stores configuration information about the view, including layout, properties, and so on. So it’s just a straightforward data structure. There are no significant performance issues when building as a structure tree, or even recreating and destroying the structure tree.

Element

Element is an abstraction of the Widget that holds the context data for the view build. The Flutter system builds RenderObject data by traversing the Element tree, so the elements are the real collection to be used and the widgets are just data structures. For example, when a view is updated, only dirty elements are marked, not dirty widgets.

RenderObject

RenderObject generates Layout, Paint, and LayerTree. So most of the plotting performance optimization in Flutter occurs here. The data constructed from the RenderObject tree is added to the LayerTree required by the Engine.

Layout

  • The purpose of Layout is to calculate the true size of the space taken up by each node.

  • Relayout boundary

The purpose of flutter is to improve the drawing performance of flutter. Its role is to set measurement boundaries. Any changes made to the widgets within the boundaries will not result in recalculation and drawing outside the boundaries.

  • constraints.isTight
  • parentUsesSize == false
  • sizedByParent == true
constraints.isTight

What is isTight? Use BoxConstraints, for example

Tight If the minimum constraint (minWidth, minHeight) and the maximum constraint (maxWidth, maxHeight) are the same respectively

Loose is both tightly and loose if the smallest constraint is 0.0 (regardless of the largest constraint), and if both the smallest constraint and the largest constraint are 0.0

Bounded if the maximum constraint is not infinite

Unbounded if the largest constraints are infinite

Expanding if the minimum constraint and the maximum constraint are infinite

So isTight is a strong constraint. The size of the Widget is already determined, and any changes made to the child widgets within it will not change the size. Then any change of any Wisget in the Widget will not have any influence on the outside, so Relayout boundary will be added (it is not scientific to add it, because in this case, it will point the size to itself, so that the Layout of the parent Widget will not be caused by upward recursion).

parentUsesSize == false

ParentUsesSize actually looks a lot like sizedByParent, but has a very different meaning. ParentUsesSize indicates whether the parent Widget depends on the size of the child Widget. If it is false, The child widgets do not need to notify the parent when they are being rearranged; the boundaries of the layout are themselves.

sizedByParent == true

SizedByParent means that the current Widget is not isTight, but size is clearly known through other constraint properties, such as Expanded, and size is not necessarily required.

You can certainly see the Layout implementation by looking at RenderObject-1579 in line

void layout(Constraints constraints, { bool parentUsesSize = false{...}) Omit the 1 w +...if(! parentUsesSize || sizedByParent || constraints.isTight || parent is! RenderObject) { relayoutBoundary = this; }else{ final RenderObject parent = this.parent; relayoutBoundary = parent._relayoutBoundary; }... Omit the 1 w +... }Copy the code

Layout shows the efforts made by Flutter to improve efficiency. Can a developer directly use Relayout Boundary? In general, you can’t, but if you decide to customize a Row, you should definitely use it. However, you can indirectly use the above three conditions to have relayout boundary somewhere in your Widget tree. For example:

Row(children: <Widget>[Expanded(child: Container(height: 50.0, // addfor test relayoutBoundary
                      child: LayoutBoundary(),
                 )),
        Expanded(child: Text('You have pushed the button this many times:')))Copy the code

If you want to test whether a layout really won’t work when the above three conditions are true, you can customize LayoutBoundaryDelegate to test, for example

class LayoutBoundaryDelegate extends MultiChildLayoutDelegate {
  LayoutBoundaryDelegate();

  static const String title = 'title';
  static const String summary = 'summary';
  static const String paintBoundary = 'paintBoundary';

  @override
  void performLayout(Size size) {
    print('TestLayoutDelegate performLayout '); final BoxConstraints constraints = BoxConstraints(maxWidth: size.width); final Size titleSize = layoutChild(title, constraints); PositionChild (title, Offset (0.0, 0.0)); final double summaryY = titleSize.height; final Size descriptionSize = layoutChild(summary, constraints); PositionChild (summary, Offset (0.0, summaryY)); final double paintBoundaryY = summaryY + descriptionSize.height; final Size paintBoundarySize = layoutChild(paintBoundary, constraints); positionChild( paintBoundary, Offset(paintBoundarySize.width / 2, paintBoundaryY)); } @override bool shouldRelayout(LayoutBoundaryDelegate oldDelegate) =>false;
}
Copy the code

Custom MultiChildLayoutDelegate needs to be used with CustomMultiChildLayout

Container(
          child: CustomMultiChildLayout(
              delegate: LayoutBoundaryDelegate(),
              children: <Widget>[
                LayoutId(
                    id: LayoutBoundaryDelegate.title,
                    child: Row(children: <Widget>[
                      Expanded(child: LayoutBoundary()),
                      Expanded(child: Text( 'You have pushed the button this many times:'))
                 ])),
                LayoutId(
                    id: LayoutBoundaryDelegate.summary,
                    child: Container(
                        child: InkWell(
                          child: Text(
                           _buttonText,
                           style: Theme.of(context).textTheme.display1),
                        onTap: () {
                          setState(() {
                            _index++;
                            _buttonText = 'onTap$_index'; }); },))), LayoutId (id: LayoutBoundaryDelegate paintBoundary, child: Container (width: 50.0, height: 50.0, the child: PaintBoundary())), ]), )Copy the code

In the performLayout method, if the size of any child in the Children of CustomMultiChildLayout changes, this information will be printed. Therefore, every time this code is clicked onTap, It will print ‘TestLayoutDelegate performLayout’

Paint

An important part of paint is determining which elements are placed in the same Layer

  • Repaint Boundary forces 2 to switch to a new Layer if the above condition occurs

class PaintBoundary extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return CustomPaint(painter: CirclePainter(color: Colors.orange));
  }
}

class CirclePainter extends CustomPainter {
  final Color color;

  const CirclePainter({this.color});

  @override
  void paint(Canvas canvas, Size size) {
    print('CirclePainter paint'); var radius = size.width / 2; var paint = Paint() .. color = color .. style = PaintingStyle.fill; canvas.drawCircle(Offset(radius, size.height), radius, paint); } @override bool shouldRepaint(CustomPainter oldDelegate) =>false;
}
Copy the code

Simply draw an orange circle which has been pasted and used in RelayoutBoundary verification code. We just have to look at the difference between setting the RepaintBoundary and not setting it. RelayoutBoundary does avoid redrawing of CirclePainter, i.e. ‘CirclePainter paint’ will only be printed once. Readers can try it out for themselves.

conclusion

Relayout Boundary and Repaint Boundary are both attempts made by Flutter to improve drawing performance. Often developers can use RepaintBoundary components to improve the performance of their applications, or they can make relayout boundary work according to several rules of relayout Boundary to improve performance.

[test code portal] (http://link.zhihu.com/?target=https%3A//github.com/Dpuntu/RePaintBoundary-RelayoutBoundary)

reference

  • Flutter’s Rendering Pipeline recommended reading
  • In-depth understanding of Flutter interface development
  • Analysis of Flutter rendering pipeline
  • Flutter principle and practice
  • A brief analysis of Flutter layout drawing process
  • The Principle of the Flutter Dart Framework is simplified

The copyright of this article belongs to Zaihui RESEARCH and development team, welcome to reprint, reprint please reserve source. @Dpuntu