Zero: preface

1. Series of introduction

The first thing you might think about Flutter painting is to use the CustomPaint component to create a custom CustomPainter object. All visible components of a Flutter, such as Text, Image, Switch, Slider, and so on, are drawn. However, most of the components of a Flutter are not drawn using the CustomPaint component. The CustomPaint component is a wrapper around the underlying painting of the framework. This series is an exploration of Flutter drawing, testing, debugging, and source code analysis to reveal things that are overlooked or never known when drawing, and points that can go wrong if omitted.

Flutter drawing exploration 1 | CustomPainter refresh the correct posture


2. Test cases in this paper

The three main groups in the Flutter framework, Element, RenderObject, and Widget, are the top-level abstractions. What is the relationship between them? What role does CustomPainter play in the light of these three? This article will reveal a little about the workings of the Flutter framework through a simplified drawing case. The following code uses the CustomPaint component directly as an input parameter to runApp.

void main() => runApp(CustomPaint(
      painter: ShapePainter(color: Colors.blue),
    ));

class ShapePainter extends CustomPainter {
  final Color color;
  ShapePainter({this.color});
  
  @override
  voidpaint(Canvas canvas, Size size) { Paint paint = Paint().. color = color; canvas.drawCircle(Offset(100.100), 50, paint);
  }
  
  @override
  bool shouldRepaint(covariant ShapePainter oldDelegate) {
    return oldDelegate.color != color;
  }
}
Copy the code

I. CustomPainter related objects

1. Introduction to the CustomPaint class

Now there are only two classes CustomPaint and CustomPainter on the table, and the following is their inheritance.


CustomPaint is a subclass of Widget. As we know, Widget is an abstract class with few features and is mainly used for Element creation.


RenderObjectWidget, however, adds the abstract method createRenderObject to create the RenderObject. So this family of widgets is responsible for creating renderObjects. RenderObjectWidget is itself an abstract class, so these two abstract methods will be implemented by its subclasses.


SingleChildRenderObjectWidget is an abstract class, inheritance from RenderObjectWidget, through its own SingleChildRenderObjectElement completed the createElement method method. So the subclass’s job is to implement the abstract method createRenderObject that creates the RenderObject.


This is where the mission sparks and the createRenderObject is implemented in CustomPaint. RenderCustomPaint is returned, so RenderCustomPaint is on the table. CustomPaint, as an implementation class for a Widget, has five final member properties that are initialized in the constructor.


2. RenderCustomPaint class description

nowRenderCustomPaintThe schematic diagram is as follows:


RenderCustomPaint is a subclass of RenderObject. RenderObject is a very complex class, and I won’t cover it in detail in this article; I’ll write a separate article later. Here’s a quick look at it: first it’s a tree structure that maintains nodes in the render tree through adoptChild and dropChild. Second, this type of object is responsible for layout and paint. The markNeedsPaint method marks the current RenderObject that needs to be painted.

abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin implements HitTestTarget {
  
  Constraints? _constraints;
  
  void layout(Constraints constraints, { bool parentUsesSize = false{...}) }void paint(PaintingContext context, Offset offset) { }
  
  @override
  void adoptChild(RenderObject child) { ... }
  
  @override
  void dropChild(RenderObject child) { ...  }
  
  void markNeedsPaint() {}
}
Copy the code

RenderBox is a direct subclass of RenderObject in the Current Flutter framework. It is a rendering object in a two-dimensional Cartesian coordinate system, with only BoxConstraints defined in RenderObject and Size further defined in RenderBox. A Size corresponds to a box in the layout range, which is a cartesian coordinate system with a top left corner of 0,0.


RenderCustomPaint is the final implementation class that is instantiated when the CustomPaint component calls createRenderObject. The constructor’s input parameters are the members of CustomPaint, which means that our CustomPainter palette will eventually be used by this class.

The current schematic diagram of the relationship between these classes is as follows:


3. CustomPainter class description

CustomPainter is an abstract class that inherits from Listenable and holds an internal member of Listenable type _repaint. It has two abstract methods paint and shouldRepaint. So to use CustomPainter, you need to inherit it and implement its abstract methods. It is held by CustomPaint and passed into the RenderCustomPaint class.

As described in the previous CustomPainter correct refresh posture, you can use Repaint to set up a listener to trigger an artboard refresh. Here’s how it works. In the RenderCustomPaint#attach method _painter adds a listener to execute the markNeedsPaint method, which triggers a redraw. CustomPainter#addListener is listening for the _repaint member to change, triggering a callback.

---->[RenderCustomPaint#attach]----
@override
void attach(PipelineOwner owner) {
  super.attach(owner); _painter? .addListener(markNeedsPaint); _foregroundPainter? .addListener(markNeedsPaint); } ---->[CustomPainter#addListener]----@override
voidaddListener(VoidCallback listener) => _repaint? .addListener(listener);Copy the code

RenderObjectElement

There is a question: when is CustomPaint#createRenderObject triggered? If something’s up in the air, tune it up. It can be seen in RenderObjectElement. Mount method is triggered.


Can have a look at the current this object, is SingleChildRenderObjectElement type, it holds a visible _widget members, it is before the incoming CustomPaint object. With widgets. CreateRenderObject for members _renderObject assignment. Into the other is this, also suggests that the participation of BuildContext object is the current SingleChildRenderObjectElement.


When surfaced “SingleChildRenderObjectElement, this diagram is rich.

Element is the top layer of Element abstraction that holds Widget objects and is derived from two main classes: ComponentElement and RenderObjectElement. Only RenderObjectElement holds the RenderObject. That is, for RenderObjectElement, both the Widget and the RenderObject are held.

To sum up, RenderObjectElement’s RenderObject member is assigned to the value RenderObjectWidget#createRenderObject returns when the mount method is executed. Here we can summarize the direct relationships of these classes.


Iii. Relationships between related categories

1.RenderObjectWidgetRenderObjectThe relationship between

RenderObjectWidget, as a Widget derivative, is responsible for creating RenderObject objects. Its subclasses must implement the createRenderObject abstract method. The relationships between the two are creator-RenderObjectwidget and creator-RenderObject. For example, for the CustomPaint component, the createRenderObject method is responsible for creating the RenderCustomPaint Object.

---->[CustomPaint#createRenderObject]----
@override
RenderCustomPaint createRenderObject(BuildContext context) {
  return RenderCustomPaint(
    painter: painter,
    foregroundPainter: foregroundPainter,
    preferredSize: size,
    isComplex: isComplex,
    willChange: willChange,
  );
}
Copy the code

2. RenderObjectElementRenderObjectThe relationship between

RenderObjectElement is an Element derived class that holds both widgets and RenderObject members. The relationship between the two is user-renderObjectelement and user-renderObject. And in the RenderObjectElement#mount method, the RenderObject member is initialized. For example: for SingleChildRenderObjectElement object RenderObject types of _renderObject is a member of that object.

-- -- -- - > [RenderObjectElement source] -abstract class RenderObjectElement extends Element {
  RenderObjectElement(RenderObjectWidget widget) : super(widget);
  @override
  RenderObjectWidget get widget => super.widget as RenderObjectWidget;

  @override
  RenderObject get renderObject => _renderObject; 
  RenderObject _renderObject;
  @override
  
  void mount(Element? parent, dynamic newSlot) {
    super.mount(parent, newSlot);
    _renderObject = widget.createRenderObject(this); // The RenderObject member is initialized
  // ...
}
Copy the code

3.WidgetElementThe relationship between

As you can see from the few lines of the Widget class, the most important purpose of a Widget is to create an Element.

Each Element holds a Widget object that creates the Element.

Additionally, Widget#createElement appears only twice in the whole Flutter framework, first when the root element node is created.

The second Element#inflateWidget is an Element incubator. All elements except the root Element node are instantiated in this method. More on this subject at an opportunity.

You should now have a simple grasp of the whole, and the following pictures will help you visualize the relationship between them.


4. Summary of CustomPainter related classes

We can see that the classes associated with CustomPainter are the CustomPaint Widget and the RenderCustomPaint RenderObject object. CustomPaint simply acts as a carrier to pass the artboard object to RenderCustomPaint. So the class most closely related to CustomPainter is the RenderCustomPaint class. This class listens on the CustomPainter listener, triggers the redraw of the RenderCustomPaint object, and CustomPainter is called back. This is also where the Canvas object in CustomPainter#paint comes from.

This article briefly reviews the relationships between draw classes. In the next article, we will explore the source code of Flutter drawing, fully analyze the CustomPainter class, and look at the timing of paint and shouldRepaint abstract methods.


@Zhang Fengjietele 2021.01.12 not allowed to transfer my public number: the king of programming contact me – email :[email protected] – wechat :zdl1994328 ~ END ~