開篇
這一篇,我們將簡單的瞭解一下 Layer
相關內容,因為其中大部分是與C++互動,所以只是從結構上做一個認識與分析
從上一篇中,我們瞭解到了,如果 RenderObject
的 isRepaintBoundary
為 true 會通過自己的 Layer
物件去渲染,如果沒有為 RenderObject
手動指定 Layer
的話,預設會是 OffestLayer
;為 false 則通過父節點的 Layer
物件渲染。
其中 paint 相關的 Layer
邏輯都在 PaintingContext
中,每次 paint 都會建立一個新的 PaintingContext
物件
同時通過 PaintingContext
獲取 Canvans
時會建立一個 PictureLayer
被合成到 PaintingContext
的建立時所接收的 Layer
中
下面,我們簡單的看一下 Layer
物件
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 ]);
...
}
複製程式碼
Layer
和 RenderObject
一樣,都是 AbstractNode
的子類,可以看到持有的 parent
物件都是 ContainerLayer
,同時 Layer
還有兩個物件 nextSibling
和 previousSibling
,看起來像一個雙向連結串列的結構
addToScene(...)
交由子類實現,就是將 Layer
物件交給 engine 去處理,傳遞給 engine 的邏輯都在 SceneBuilder
中
Layer
有多個子類,分別實現不同的渲染功能
其中 PictureLayout
是主要的影像繪製層;
TextureLayer
則用於外界紋理的實現,通過它可以實現諸如相機、視訊播放、OpenGL等相關操作;
ContainerLayout
則是各個 Layer
組成的複合層
Layer的重新整理
上一篇中,我們自定義了 RenderObject
並且重寫了它的 paint(...)
方法,通過對 Canvans
物件進行操作,我們繪製了自己想要的影像,而 Canvans
是從 PaintingContext
中獲取的,在獲取 Canvans
時,其實做了和 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);
}
...
}
複製程式碼
可以看到,在這裡建立了一個新的 PictureLayer
被新增到了 _containerLayer
中,我們的 Layer
最終是如何被渲染的呢?
資訊還是可以從上一篇獲得,我們知道 RendererBinding
的 drawFrame()
中進行了佈局與繪製操作
@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;
}
}
複製程式碼
最終的渲染其實是通過 compositeFrame()
來進行的,而這裡的 renderView
就是我們的根RenderObject
我們可以看一下 compositeFrame()
做了些什麼
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();
...
}
...
}
複製程式碼
可以看到,這裡通過 buildScene(...)
建立了 Scene
物件,根據註釋來看,它相當於一顆 Layer
樹
之後通過 Window
物件的 render(...)
方法來進行渲染
void render(Scene scene) native 'Window_render';
複製程式碼
它直接呼叫的是 engine 的方法,通過這個方法,就可以對影像進行渲染,後面我們會進行一個測試來展示它的作用
這裡的 layer
就是根 Layer
,它其實是一個 TransformLayer
,我們可以簡單看一下它的建立流程
根Layer建立流程
在 RendererBinding
的 initInstances()
中通過 initRenderView()
進行了 RenderView
的建立
mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, SemanticsBinding, HitTestable {
...
@override
void initInstances() {
super.initInstances();
...
initRenderView();
...
}
...
}
複製程式碼
RendererBinding -> initRenderView()
void initRenderView() {
assert(renderView == null);
renderView = RenderView(configuration: createViewConfiguration(), window: window);
renderView.prepareInitialFrame();
}
複製程式碼
根 Layer
就是在 prepareInitialFrame()
中建立的
RenderView -> prepareInitialFrame()
void prepareInitialFrame() {
...
scheduleInitialLayout();
scheduleInitialPaint(_updateMatricesAndCreateNewRootLayer());
...
}
複製程式碼
建立的方法就是 _updateMatricesAndCreateNewRootLayer()
TransformLayer _updateMatricesAndCreateNewRootLayer() {
_rootTransform = configuration.toMatrix();
final TransformLayer rootLayer = TransformLayer(transform: _rootTransform);
rootLayer.attach(this);
...
return rootLayer;
}
複製程式碼
這裡我們只是簡單的瞭解一下根 Layer
的建立流程,它就是一個 TransformLayer
物件。
建立流程我們知道了,而想要了解重新整理流程,我們需要回到 compositeFrame()
方法中,執行重新整理的方法就在 buildScene(...)
裡
buildScene(...)
從前面的關係圖我們知道,TransformLayer
的父類是 OffsetLayer
,而 OffsetLayer
的父類是 ContainerLayer
,它們都沒有重寫 buildScene(...)
方法,所以最後會呼叫 ContainerLayer
的 buildScene(...)
ui.Scene buildScene(ui.SceneBuilder builder) {
...
updateSubtreeNeedsAddToScene();
addToScene(builder);
...
_needsAddToScene = false;
...
return scene;
}
複製程式碼
可以先看一下 updateSubtreeNeedsAddToScene()
方法
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
void updateSubtreeNeedsAddToScene() {
_needsAddToScene = _needsAddToScene || alwaysNeedsAddToScene;
}
複製程式碼
其實所有 Layer
類中,只有 ConstraintLayer
和 Layer
具備這兩個方法,這裡其實就是遍歷所有子 Layer
物件,呼叫他們的 updateSubtreeNeedsAddToScene()
來設定 _needsAddToScene
的值
這個值顧名思義,就是表示是否需要將改 Layer
新增到 Scene
中,如果需要新增,則就是進行重新整理了。它根據 _needsAddToScene
和 alwaysNeedsAddToScene
來設定,當呼叫 markNeedsAddToScene()
方法的時候, _needsAddToScene
就會被設定為 true
updateSubtreeNeedsAddToScene()
執行結束後,接下來會呼叫 addToScene(builder)
方法
addToScene(...)
正好 TransformLayer
重寫了這個方法,並且沒有呼叫父類的方法
@override
void addToScene(ui.SceneBuilder builder, [ Offset layerOffset = Offset.zero ]) {
...
engineLayer = builder.pushTransform(
_lastEffectiveTransform.storage,
oldLayer: _engineLayer as ui.TransformEngineLayer,
);
addChildrenToScene(builder);
builder.pop();
}
複製程式碼
這裡的 engineLayer
物件是用於進行復用的
可以看到這裡呼叫了 addChildrenToScene(builder)
方法,這個方法只在 ContainerLayer
中,且沒有被重寫
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;
}
}
複製程式碼
在這裡就是遍歷 child ,然後呼叫它們各自實現的 addToScene(...)
方法,而是否要將 Layer
新增到 Scene
的判斷依據,已經在之前的 updateSubtreeNeedsAddToScene()
中完成了。
這裡需要注意一下, _addToSceneWithRetainedRendering(builder)
就是用於對之前的 _engineLayer
進行復用,當 childOffset
為 Offset.zero
時
那麼到這裡 Layer
的重新整理流程就結束了。而本篇文章差不多也快到頭了,接下來我們完成上面提到過的,進行一個渲染測試
渲染測試
可以在這裡進行測試: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();
}
複製程式碼
效果如下
可以看到,我們沒有使用任何 Widget
就在裝置上展示了一個圖案。所以其實從這裡就可以瞭解到為什麼說 Flutter是通過skia引擎去繪製的了。
關於 Layer
的其他內容,這裡也不再深入了,畢竟再深入就是C++了
本篇是我們講過的四棵樹中最後的一顆,而這裡非常方便用於測試一個我們前三篇都遇到了但是都略過了的部分,那就是 熱過載
額外部分:熱過載
當你通過上面的用例進行測試的時候,點選一下熱過載按鈕,是不是發現會報錯:
Error -32601 received from application: Method not found
複製程式碼
並且圖案的顏色並不會更改,這就涉及到我們之前提到過的一個方法了:reassemble()
了
在 Element
和 RenderObject
中你經常能看到與之相關的方法,它就是用於實現熱過載的核心邏輯
在 BindingBase
中,我們可以看到找到這樣一個方法:reassembleApplication()
,就是它來進行熱過載控制的
它會呼叫 performReassemble()
方法
@mustCallSuper
@protected
Future<void> performReassemble() {
FlutterError.resetErrorCount();
return Future<void>.value();
}
複製程式碼
在 WidgetsBinding
和 RendererBinding
都重寫了這個方法,如果感興趣的話,可以去看一下,他們分在其中呼叫了讓 Element
和 RenderObject
進行熱過載的方法
那麼,我們想要實現實現熱過載其實就很簡單了,看程式碼:
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();
return Future<void>.value();
}
}
複製程式碼
熱過載效果如下,大家可以在裝置上進行測試
當然,熱過載的核心邏輯就是這個了。
不過此前會進行程式碼檔案的變更檢查等,詳情可以看這一篇文章:揭祕Flutter Hot Reload(原理篇)
本篇到這裡就結束了,而【從原始碼看flutter】 尚未結束,敬請期待吧