原文, Flutter, what are Widgets, RenderObjects and Elements?

Have you ever wondered how Flutter handles Widgets and transforms them into pixels for display on the screen? Has not yet?

You should think about it.

Understanding the underlying implementation principles of a system is what differentiates a good programmer.

When you know what works best, it’s easier to create layouts and effects, saving you a lot of time.

The purpose of this article is to introduce you to the inner workings of Flutter. We will look at Flutter from different angles and understand exactly how Flutter works.

Let’s get started

You probably already know how to use statelessWidgets and StatefulWidgets, but they are just containers for assembling controls; the layout and drawing work is done elsewhere.

I highly recommend that you open up your favorite IDE and keep reading. It’s only when you look at the actual code that you feel “oh, that’s it”. In Intellij, you can find the code by double-clicking Shift and typing the class name.

Opacity

To familiarize yourself with the basics of how Flutter works, a good example would be to look at the most basic control, Opacity.

Opacity receives a child, so you can use Opacity to wrap any Widget to change its appearance. It also receives a property called opacity, which sets the control’s opacity between 0.0 and 1.0.

Widget

Opacity is a SingleChildRenderObjectWidget.

This class inherits as follows:

The Opacity – SingleChildRenderObjectWidget – RenderObjectWidget > widgets

Accordingly, the inheritance relationship between StatelessWidget and StatefulWidget is as follows:

StatelessWidget/StatefulWidget – > widgets

The difference is that Stateless/StatefulWidget simply assembles other widgets, while Opacity actually affects the drawing of the Widget.

But if you look in the code, you won’t be able to find any drawing code related to the opacity property.

That’s because the Widget only holds configuration information for the control. In this example, the control Opacity is only used to hold the property Opacity.

This is why you can create a new widget in the build() function every time. The process of building widgets is not resource-intensive because Wiget is just a container for holding properties.

Rendering

So where is the rendering done?

The answer is RenderingObject.

As you might guess from the name, RenderingObject is responsible for rendering.

Opacity RenderingObject is created and updated using the following methods:

@override
RenderOpacity createRenderObject(BuildContext context) => new RenderOpacity(opacity: opacity);

@override
void updateRenderObject(BuildContext context, RenderOpacity renderObject) {
  renderObject.opacity = opacity;
}
Copy the code

The source code

RenderOpacity

Opacity is almost identical to its child except for drawing, and it uses the size of the child as its own size. It adds an opacity to its child before drawing it.

Therefore, RenderOpacity needs to implement all functions, including layout, click detection, and calculation of size, and transfer them to its child (i.e. a child’s proxy).

RenderOpacity inherits from the RenderProxyBox, which implements work transition to child.

double get opacity => _opacity;
double _opacity;
set opacity(double value) {
  _opacity = value;
  markNeedsPaint();
}
Copy the code

Complete source code

In addition to setting the value of the field, markNeedsPaint() (or markNeedsLayout()) is called in the setter method, which, as the name implies, tells the system “I have changed, please redraw (or layout).”

In RenderOpacity, we find the following method:

@override
void paint(PaintingContext context, Offset offset) {
    context.pushOpacity(offset, _alpha, super.paint);
}
Copy the code

Complete source code

PaintingContext is the canvas on which to draw, and opacity is controlled by calling a method called pushOpacity on the canvas.

Review the

  • OpacitynotStatelessWidgetorStatefulWidget, butSingleChildRenderObjectWidget;
  • Widgets are only used to store information needed for rendering;
  • Here,OpacityStores a double value of opacity;
  • Operations such as layout and rendering are actually inherited fromRenderProxyBoxRenderOpacityThe finished;
  • becauseOpacityDoes not change the child’s other behavior, so each of its methods is simply a proxy for the child;
  • By overloadingpaintMethod and callpushOpacityRenderOpacity implements the need to add opacity to widgets.

That ‘s it? Kind of.

Remember that the Widget is just a configuration, and RenderObject is responsible for managing layout, drawing, and so on.

In Flutter, you are basically creating Widgets all the time. When the build() method is called, you create a bunch of Widgets.

The build() method is called every time something changes. For example, when playing an animation, the build() method is called frequently. This means that you can’t always rebuild the entire render tree, instead, you should do the knowledge to update the tree.

You can’t get the location or size of a widget on the screen, because a widget is like a blueprint; it doesn’t actually appear on the screen, it just describes the properties that the underlying render object should have.

Element

Element is the entity in the tree of this large control.

Basically what happens:

When a Widget is created for the first time, an Element is created and inserted into the tree. If the Widget changes later, it is compared to the old Widget and the Element is updated accordingly. Importantly, Element is not rebuilt, only updated.

Elements are an important part of the Core framework of The Flutter, and it’s obviously more than that, but that’s all we need to know for now.

In the Opacity example, where is element created?

SingleChildRenderObjectWidget created it:

@override
SingleChildRenderObjectElement createElement() => new SingleChildRenderObjectElement(this);
Copy the code

The source code

The SingleChildRenderObjectElement is a have only one child element.

Element creates the RenderObject, but in the example the RenderObject is created by the Widget?

This is just for smooth API purposes, because it is common for widgets to require a RenderObject rather than a custom Element. The RenderObject is actually created by Element, so let’s take a look.

SingleChildRenderObjectElement(SingleChildRenderObjectWidget widget) : super(widget);
Copy the code

The source code

In the constructor SingleChildRenderObjectElement got a RenderObjectWidget reference (including the method of creating RenderObject).

Element is inserted into the Element Tree via mount method, and this is where Element creates the RenderObject:

@override
void mount(Element parent, dynamic newSlot) {
  super.mount(parent, newSlot);
  _renderObject = widget.createRenderObject(this);
  attachRenderObject(newSlot);
  _dirty = false;
}
Copy the code

The source code

Once an Element is mounted to the tree, it asks the Widget “Please give me the RenderObject you want to use so I can save it.”

conclusion

This is how the inside of the Opacity control works. The goal of this article is to introduce you to the world outside of widgets. There’s still a lot to discuss here, but I hope you have a good understanding of how it works.