Inside the RenderOpacity we find this method:

@override

void paint(PaintingContext context, Offset offset) {

context.pushOpacity(offset, _alpha, super.paint);

}

Again, removed optimization and assertions. source

The PaintingContext is basically a fancy canvas. And on this fancy canvas there is a method called pushOpacity. BINGO.

This one line is the actual opacity implementation.

To recap

The Opacity is not a StatelessWidget or a StatefulWidget but instead a SingleChildRenderObjectWidget .

is not a or a but instead a . The Widget only holds information which the renderer can use.

only holds information which the renderer can use. In this case the Opacity is holding a double representing the opacity.

is holding a double representing the opacity. The RenderOpacity , which extends the RenderProxyBox does the actual layouting/ rendering etc.

, which extends the does the actual layouting/ rendering etc. Because the opacity behaves pretty much exactly as its child it delegates every method call to the child.

It overrides the paint method and calls pushOpacity which adds the desired opacity to the widget.

That’s it? Kind of.

Remember, the widget is only a configuration and the RenderObject only manages layout/rendering etc.

In Flutter you recreate widgets basically all the time. When your build() methods gets called you create a bunch of widgets. This build method is called every time something changes. When an animation happens for example, the build method gets called very often. This means you can’t rebuild the whole sub tree every time. Instead you want to update it.

You can’t get the size or location on the screen of a widget, because a widget is like a blueprint, it’s not actually on the screen. It’s only a description of what variables the underlying render object should use.

Introducing the Element

The element is a concrete widget in the big tree.

Basically what happens is:

The first time when a widget is created, it is inflated to an Element . The element then gets inserted it into the tree. If the widget later changes, it is compared to the old widget and the element updates accordingly. The important thing is, the element doesn’t get rebuilt, it only gets updated.

Elements are a central part of the core framework and there is obviously more to them, but for now this is enough information.

Where is the element created in the opacity example?

Just a little paragraph for those who are curios.

The SingleChildRenderObjectWidget creates it.

@override

SingleChildRenderObjectElement createElement() => new SingleChildRenderObjectElement(this);

source

And the SingleChildRenderObjectElement is just an Element which has single child.

The element creates the RenderObject, but in our case the Opacity widget creates its own RenderObject?

This is just for a smooth API. Because more often then not, the widget needs a RenderObject but no custom Element . The RenderObject is actually created by the Element , let’s take a look:

SingleChildRenderObjectElement(SingleChildRenderObjectWidget widget) : super(widget);

source

The SingleChildRenderObjectElement gets a reference to the RenderObjectWidget (which has the methods to create a RenderObject ).

The mount method is the place where the element gets inserted into the element tree, and here the magic happens (RenderObjectElement class):

@override

void mount(Element parent, dynamic newSlot) {

super.mount(parent, newSlot);

_renderObject = widget.createRenderObject(this);

attachRenderObject(newSlot);

_dirty = false;

}

(Right after super.mount(parent,newSlot); source

Only once (when it get’s mounted) it asks the widget “Please give me the renderobject you’d like to use so I can save it”.