從源碼看flutter系列集合canvas
這一篇,咱們將簡單的瞭解一下 Layer
相關內容,由於其中大部分是與C++交互,因此只是從結構上作一個認識與分析bash
從上一篇中,咱們瞭解到了,若是 RenderObject
的 isRepaintBoundary
爲 true 會經過本身的 Layer
對象去渲染,若是沒有爲 RenderObject
手動指定 Layer
的話,默認會是 OffestLayer
;爲 false 則經過父節點的 Layer
對象渲染。app
其中 paint 相關的 Layer
邏輯都在 PaintingContext
中,每次 paint 都會建立一個新的 PaintingContext
對象dom
同時經過 PaintingContext
獲取 Canvans
時會建立一個 PictureLayer
被合成到 PaintingContext
的建立時所接收的 Layer
中ide
下面,咱們簡單的看一下 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
,看起來像一個雙向鏈表的結構post
addToScene(...)
交由子類實現,就是將 Layer
對象交給 engine 去處理,傳遞給 engine 的邏輯都在 SceneBuilder
中測試
Layer
有多個子類,分別實現不一樣的渲染功能ui
其中 PictureLayout
是主要的圖像繪製層;
TextureLayer
則用於外界紋理的實現,經過它能夠實現諸如相機、視頻播放、OpenGL等相關操做;
ContainerLayout
則是各個 Layer
組成的複合層this
上一篇中,咱們自定義了 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
,咱們能夠簡單看一下它的建立流程
在 RendererBinding
的 initInstances()
中經過 initRenderView()
進行了 RenderView
的建立
mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, SemanticsBinding, HitTestable {
...
@override
void initInstances() {
super.initInstances();
...
initRenderView();
...
}
...
}
複製代碼
void initRenderView() {
assert(renderView == null);
renderView = RenderView(configuration: createViewConfiguration(), window: window);
renderView.prepareInitialFrame();
}
複製代碼
根 Layer
就是在 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(...)
裏
從前面的關係圖咱們知道,TransformLayer
的父類是 OffsetLayer
,而 OffsetLayer
的父類是 ContainerLayer
,它們都沒有重寫 buildScene(...)
方法,因此最後會調用 ContainerLayer
的 buildScene(...)
ui.Scene buildScene(ui.SceneBuilder builder) {
...
updateSubtreeNeedsAddToScene();
addToScene(builder);
...
_needsAddToScene = false;
...
return scene;
}
複製代碼
能夠先看一下 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)
方法
正好 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
中,且沒有被重寫
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】 還沒有結束,敬請期待吧