Read the Flutter collection from source code
The opening
In this article, we will briefly take a look at the Layer related content, because most of it is interacting with C++, so it is only a structural understanding and analysis
If the RenderObject’s isRepaintBoundary is true, the RenderObject will be rendered using its own Layer object. If the Layer is not specified manually, the RenderObject will be rendered using the RenderObject’s isRepaintBoundary. The default is OffestLayer; False renders through the parent’s Layer object.
The Layer logic for paint is in the PaintingContext, and each paint context creates a new PaintingContext object
At the same time retrieving Canvans from the PaintingContext creates a PictureLayer that is synthesized into the Layer received when the PaintingContext is created
Now, let’s take a quick look at the Layer object
Layer
abstract class Layer extends AbstractNode with DiagnosticableTreeMixin { @override ContainerLayer get parent => super.parent as ContainerLayer; . Layer get nextSibling => _nextSibling; . Layer get previousSibling => _previousSibling; . @protected void addToScene(ui.SceneBuilder builder, [ Offset layerOffset = Offset.zero ]); . }Copy the code
Both Layer and RenderObject are subclasses of AbstractNode, and both parent objects are ContainerLayer. Layer also has two objects, nextSibling and previousSibling, which looks like a bidirectional list structure
addToScene(…) By subclass implementation, the Layer object is passed to engine, and the logic passed to Engine is in SceneBuilder
Layer has several subclasses that implement different rendering functions
PictureLayout is the main image drawing layer. TextureLayer is used for external texture implementation. It can be used for camera, video playback, OpenGL and other related operations. ContainerLayout is a composite Layer of each Layer
Layer of the refresh
In the previous article, we customized the RenderObject and overwrote its paint(…). Method, we draw the image we want by manipulating the Canvans object, and the Canvans is getting it from the PaintingContext. When we get the Canvans, we actually do a series of operations related to the Layer
class PaintingContext extends ClipContext {
...
@override
Canvas get canvas {
if (_canvas == null)
_startRecording();
return _canvas;
}
void _startRecording() {
assert(!_isRecording);
_currentLayer = PictureLayer(estimatedBounds);
_recorder = ui.PictureRecorder();
_canvas = Canvas(_recorder);
_containerLayer.append(_currentLayer);
}
...
}
Copy the code
So you can see that we’ve created a new PictureLayer and added it to the _containerLayer, so how does our Layer end up being rendered?
The information is still available from the previous article, where we know that RendererBinding’s drawFrame() is used for layout and drawing
@protected
void drawFrame() { assert(renderView ! = null); pipelineOwner.flushLayout(); pipelineOwner.flushCompositingBits(); pipelineOwner.flushPaint();if (sendFramesToEngine) {
renderView.compositeFrame(); // this sends the bits to the GPU
pipelineOwner.flushSemantics(); // this also sends the semantics to the OS.
_firstFrameSent = true; }}Copy the code
The final render is actually done with the compositeFrame(), and the renderView here is our root RenderObject and we can look at what does the compositeFrame() do
class RenderView extends RenderObject with RenderObjectWithChildMixin<RenderBox> {
...
void compositeFrame() {... final ui.SceneBuilder builder = ui.SceneBuilder(); final ui.Scene scene = layer.buildScene(builder);if(automaticSystemUiAdjustment) _updateSystemChrome(); _window.render(scene); scene.dispose(); . }... }Copy the code
You can see that by buildScene(…) Creates the Scene object, which, according to the comments, is equivalent to a Layer tree
Then render(…) the Window object. Method to render
void render(Scene scene) native 'Window_render';
Copy the code
It directly calls engine’s method, which renders the image, and we’ll do a test to show what it does
The layer here is the root layer, which is actually a TransformLayer, and we can briefly look at the process of creating it
Root Layer creation process
RenderView is created using initInstances() in RendererBinding
mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, SemanticsBinding, HitTestable {
...
@override
void initInstances() { super.initInstances(); . initRenderView(); . }... }Copy the code
RendererBinding -> initRenderView()
void initRenderView() {
assert(renderView == null);
renderView = RenderView(configuration: createViewConfiguration(), window: window);
renderView.prepareInitialFrame();
}
Copy the code
The root Layer is created in prepareInitialFrame()
RenderView -> prepareInitialFrame()
void prepareInitialFrame() {... scheduleInitialLayout(); scheduleInitialPaint(_updateMatricesAndCreateNewRootLayer()); . }Copy the code
The way to create is _updateMatricesAndCreateNewRootLayer ()
TransformLayer _updateMatricesAndCreateNewRootLayer() { _rootTransform = configuration.toMatrix(); final TransformLayer rootLayer = TransformLayer(transform: _rootTransform); rootLayer.attach(this); .return rootLayer;
}
Copy the code
Here we just briefly look at the process of creating the root Layer, which is a TransformLayer object.
We know the creation process, and to understand the refresh process we need to go back to the compositeFrame() method, which is done in buildScene(…). In the
buildScene(…)
As we know from the diagram above, the TransformLayer parent is OffsetLayer, and the OffsetLayer parent is ContainerLayer, and they don’t override buildScene(…). Method, so it will finally call ContainerLayer’s buildScene(…)
ui.Scene buildScene(ui.SceneBuilder builder) { ... updateSubtreeNeedsAddToScene(); addToScene(builder); . _needsAddToScene =false; .return scene;
}
Copy the code
Can have a look at the first updateSubtreeNeedsAddToScene () method
updateSubtreeNeedsAddToScene()
///ConstraintLayer
@override
void updateSubtreeNeedsAddToScene() {
super.updateSubtreeNeedsAddToScene();
Layer child = firstChild;
while(child ! = null) { child.updateSubtreeNeedsAddToScene(); _needsAddToScene = _needsAddToScene || child._needsAddToScene; child = child.nextSibling; } } ///Layer @protected @visibleForTesting voidupdateSubtreeNeedsAddToScene() {
_needsAddToScene = _needsAddToScene || alwaysNeedsAddToScene;
}
Copy the code
ConstraintLayer and Layer are the only Layer classes that have these methods, so we’re essentially iterating through all of the sublayer objects, Call their updateSubtreeNeedsAddToScene () to set the value of _needsAddToScene
As the name implies, this value indicates whether the change Layer needs to be added to the Scene, and if so, it is refreshed. It is set based on _needsAddToScene and alwaysNeedsAddToScene. When markNeedsAddToScene() is called, _needsAddToScene is set to true
UpdateSubtreeNeedsAddToScene () after the execution, then call addToScene (builder) method
addToScene(…)
It happens that the TransformLayer overwrites this method and does not call the superclass method
@override
void addToScene(ui.SceneBuilder builder, [ Offset layerOffset = Offset.zero ]) {
...
engineLayer = builder.pushTransform(
_lastEffectiveTransform.storage,
oldLayer: _engineLayer as ui.TransformEngineLayer,
);
addChildrenToScene(builder);
builder.pop();
}
Copy the code
The engineLayer object here is intended for reuse
You can see that the addChildrenToScene(Builder) method is called, which is only in ContainerLayer and has not been overridden
addChildrenToScene(…)
void addChildrenToScene(ui.SceneBuilder builder, [ Offset childOffset = Offset.zero ]) {
Layer child = firstChild;
while(child ! = null) {if (childOffset == Offset.zero) {
child._addToSceneWithRetainedRendering(builder);
} else{ child.addToScene(builder, childOffset); } child = child.nextSibling; }}Copy the code
In this case, we iterate over the child and call their respective implementations of addToScene(…) Method, and whether to add Layer to the judgment of Scene, has been in previous updateSubtreeNeedsAddToScene (in) completed.
Note here that _addToSceneWithRetainedRendering (builder) is used in the reuse of previous _engineLayer when childOffset Offset. Zero
Then the Layer refresh process is finished at this point. Now that this article is almost over, let’s finish what I mentioned above and do a rendering test
Rendering test
You can test it here: dartpad.dev/
import 'dart:ui';
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
void main(){
final OffsetLayer rootLayer = new OffsetLayer();
final PictureLayer pictureLayer = new PictureLayer(Rect.zero);
rootLayer.append(pictureLayer);
PictureRecorder recorder = PictureRecorder();
Canvas canvas = Canvas(recorder);
Paint paint = Paint();
paint.color = Colors.primaries[Random().nextInt(Colors.primaries.length)];
canvas.drawRect(Rect.fromLTWH(0, 0, 300, 300), paint);
pictureLayer.picture = recorder.endRecording();
SceneBuilder sceneBuilder = SceneBuilder();
rootLayer.addToScene(sceneBuilder);
Scene scene = sceneBuilder.build();
window.onDrawFrame = (){
window.render(scene);
};
window.scheduleFrame();
}
Copy the code
Results the following
As you can see, we are displaying a pattern on the device without using any widgets. This is why Flutter is drawn by the SKia engine.
I won’t go into the rest of the Layer here, because it’s C++ after all
This is the last of the four trees we’ve covered, and it’s a very handy way to test an area that we’ve covered in the previous three articles but skipped: hot overloading
Bonus: Thermal overload
When you test through the above use case, click on the hot reload button to see if you get an error:
Error -32601 received from application: Method not found
Copy the code
And the colour of the pattern doesn’t change, which brings us to the method we mentioned earlier: Reassemble ()
The method you often see associated with Element and RenderObject is the core logic used to implement hot overloading
In BindingBase, we can see that a method is found: reassembleApplication(), which does the hot overload control
It calls the performReassemble() method
@mustCallSuper
@protected
Future<void> performReassemble() {
FlutterError.resetErrorCount();
return Future<void>.value();
}
Copy the code
This method is overridden in both WidgetsBinding and RendererBinding, if you’re interested, where they call hot-overload methods for Element and RenderObject
So, we want to implement hot overloading is actually very simple, look at the code:
import 'dart:ui';
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/foundation.dart';
void main() => TestBinding();
class TestBinding extends BindingBase{
@override
Future<void> performReassemble(){
final OffsetLayer rootLayer = new OffsetLayer();
final PictureLayer pictureLayer = new PictureLayer(Rect.zero);
rootLayer.append(pictureLayer);
PictureRecorder recorder = PictureRecorder();
Canvas canvas = Canvas(recorder);
Paint paint = Paint();
paint.color = Colors.primaries[Random().nextInt(Colors.primaries.length)];
canvas.drawRect(Rect.fromLTWH(0, 0, 300, 300), paint);
pictureLayer.picture = recorder.endRecording();
SceneBuilder sceneBuilder = SceneBuilder();
rootLayer.addToScene(sceneBuilder);
Scene scene = sceneBuilder.build();
window.onDrawFrame = (){
window.render(scene);
};
window.scheduleFrame();
super.performReassemble();
returnFuture<void>.value(); }}Copy the code
The thermal overload effect is shown below, and you can test it on your device
That, of course, is the core logic of hot overloading.
Changes to the code file will be checked before the changes are made. For details, see this article:
This article ends here, but flutter is not finished yet, so stay tuned