在Widget
篇中,提到了RenderObject
,那麼RenderObject
究竟是個啥?我們來盤一盤!node
An object in the render tree.
緩存
render樹上的一個對象。bash
Flutter門前有四棵樹,一棵是Widget
樹,一棵是Element
樹,一棵是Render
樹,還有一棵是layer
樹。ide
若是Widget
樹是一張圖紙,那麼Render
樹就是這張圖紙對應的流水線。RenderObject
就是這條流水線上的操做員工。佈局
咱們在頂層配置好Widget
樹後,最終render
樹負責在手機屏幕上表現出你想要的畫面。性能
美好的一天應當從閱讀源碼開始!測試
abstract class RenderObject extends AbstractNode
with DiagnosticableTreeMixin
implements HitTestTarget {}
複製代碼
能夠看到RenderObject
也是鏈表結構,混入了DiagnosticableTreeMixin
樹狀結構的特性,而且實現了命中測試抽象類。ui
去掉debug和assert的代碼,RenderObject
的結構功能清晰可見。this
abstract class RenderObject {
// LAYOUT 模塊
// 傳遞信息給child的存儲容器 一般爲偏移量Offset
ParentData parentData;
// 此方法,在child加入到child列表以前,把parentData傳遞給child
void setupParentData(covariant RenderObject child) {
if (child.parentData is! ParentData)
child.parentData = ParentData();
}
// 添加一個render object 做爲本身的child
@override
void adoptChild(RenderObject child) {
// 設置parentData
setupParentData(child);
// 標記更新
markNeedsLayout();
markNeedsCompositingBitsUpdate();
markNeedsSemanticsUpdate();
super.adoptChild(child);
}
// 刪除一個render object 做爲本身的child
@override
void dropChild(RenderObject child) {
// 清除邊界
child._cleanRelayoutBoundary();
// 失去parentdata訪問權限
child.parentData.detach();
child.parentData = null;
super.dropChild(child);
markNeedsLayout();
markNeedsCompositingBitsUpdate();
markNeedsSemanticsUpdate();
}
// 遍歷children
void visitChildren(RenderObjectVisitor visitor) { }
// render tree的管理者
@override
PipelineOwner get owner => super.owner;
// 告訴其owner將其插入render tree 並初次標記須要計算layout而且重繪
@override
void attach(PipelineOwner owner) {
super.attach(owner);
if (_needsLayout && _relayoutBoundary != null) {
_needsLayout = false;
markNeedsLayout();
}
if (_needsCompositingBitsUpdate) {
_needsCompositingBitsUpdate = false;
markNeedsCompositingBitsUpdate();
}
if (_needsPaint && _layer != null) {
_needsPaint = false;
markNeedsPaint();
}
if (_needsSemanticsUpdate && _semanticsConfiguration.isSemanticBoundary) {
_needsSemanticsUpdate = false;
markNeedsSemanticsUpdate();
}
}
// 是否須要佈局
bool _needsLayout = true;
// 記錄佈局邊界在哪
RenderObject _relayoutBoundary;
// parent的約束
@protected
Constraints get constraints => _constraints;
Constraints _constraints;
//標記須要從新layout
void markNeedsLayout() {
// 若是本身不是邊界,則讓parent.markNeedsLayout處理 一直推到邊界
if (_relayoutBoundary != this) {
markParentNeedsLayout();
} else {
// owner 把本身加入layout的髒表 請求刷新
_needsLayout = true;
if (owner != null) {
owner._nodesNeedingLayout.add(this);
owner.requestVisualUpdate();
}
}
}
// 遍歷清除非邊界child relayout邊界記錄 當邊界發生變化時,會調用此方法清除邊界緩存
void _cleanRelayoutBoundary() {
if (_relayoutBoundary != this) {
_relayoutBoundary = null;
_needsLayout = true;
visitChildren((RenderObject child) {
child._cleanRelayoutBoundary();
});
}
}
// 僅layout 不從新測量大小
void _layoutWithoutResize() {
performLayout();
markNeedsSemanticsUpdate();
_needsLayout = false;
markNeedsPaint();
}
void layout(Constraints constraints, { bool parentUsesSize = false }) {
RenderObject relayoutBoundary;
// 肯定邊界
// 知足如下四項條件 中的一項 則本身爲邊界
// parentUsesSize parent是否關心本身的大小
// sizedByParent 由parent確認大小
// constraints.isTight 受嚴格約束
// parent 不爲 RenderObject 即本身爲root節點
if (!parentUsesSize || sizedByParent || constraints.isTight || parent is! RenderObject) {
relayoutBoundary = this;
} else {
// 不然使用parent的邊界
final RenderObject parent = this.parent;
relayoutBoundary = parent._relayoutBoundary;
}
_constraints = constraints;
// 若是邊界發生變化 則遍歷清空全部已記錄的邊界 從新設置
if (_relayoutBoundary != null && relayoutBoundary != _relayoutBoundary) {
visitChildren((RenderObject child) {
child._cleanRelayoutBoundary();
});
}
_relayoutBoundary = relayoutBoundary;
// sizedByParent 爲true 時 纔會調用performResize 肯定大小
// 不然在performLayout中肯定size
if (sizedByParent) {
performResize();
}
// 計算layout
performLayout();
markNeedsSemanticsUpdate();
_needsLayout = false;
markNeedsPaint();
}
// size是否只由parent的約束決定 默認爲false 永遠不會錯
// 當爲true時,肯定size的工做要在performResize中計算
@protected
bool get sizedByParent => false;
// 子類重寫此方法 根據約束 計算自身的size
// 不能直接調用此方法 而是調用layout方法間接調用 只有sizedByParent爲true時纔會執行
@protected
void performResize();
// 子類重寫此方法
// 不能直接調用此方法 而是調用layout方法間接調用
// 做用1:當sizedByParent爲false時 根據適應child計算自身size
// 做用2:遍歷調用child.layout 肯定child的size和offsets
@protected
void performLayout();
}
複製代碼
劃一下重點:spa
RenderObject
做爲Render Tree
上的一個對象,可是本身卻不能直接改變這棵大樹。一舉一動都須要傳達給PipelineOwner
這位管理者去執行。PipelineOwner
管理着幾個污污的(dirty)小本本,記錄了須要變化的節點,根據這些小本本去實際更新Render Tree
。當RenderObject
須要發生改變時,通知owner把本身寫到小本本上(標記爲dirty)。能夠理解爲軍訓過程當中,有什麼問題報告給教官,教官統一安排。parent
經過setupParentData
方法,傳遞parentData
,一般爲layout offset
。performResize
方法須要子類重寫。計算自身的大小performLayout
方法須要子類重寫。layout
。肯定`_relayoutBoundary`佈局邊界
->調用`performResize`和`performLayout`方法計算大小和位置
->重繪
複製代碼
因爲這個流程知足大多數場景,所以當咱們真正開發時,只關心重寫performResize
和performLayout
的實現,而不會去重寫layout
方法。
abstract class RenderObject {
// PAINTING 模塊
RenderObject() {
// 是否須要混合圖層 = 是重繪邊界 || 老是混合圖層
_needsCompositing = isRepaintBoundary || alwaysNeedsCompositing;
}
// 當前是否爲重繪邊界
bool get isRepaintBoundary => false;
// 是否老是新建圖層而後合併到原圖層
@protected
bool get alwaysNeedsCompositing => false;
// 緩存的layer
ContainerLayer _layer;
// 是否_needsCompositing的值須要設置
bool _needsCompositingBitsUpdate = false;
// 標記_needsCompositing的值須要從新設置
void markNeedsCompositingBitsUpdate() {
if (owner != null)
owner._nodesNeedingCompositingBitsUpdate.add(this);
}
// 是否在一個新的圖層繪製而後合併到祖先圖層
// true:在新圖層繪製 可是新圖層會優先使用緩存圖層 以提升性能
// false:不使用新圖層 此時緩存圖層必定要置null
// 當前爲repaintBoundary時 _needsCompositing=true 而且會自動給緩存layer賦值爲新的OffsetLayer 在此layer上繪製後 合併到祖先圖層
bool _needsCompositing;
// 遍歷設置須要使用圖層混合
void _updateCompositingBits() {
if (!_needsCompositingBitsUpdate)
return;
final bool oldNeedsCompositing = _needsCompositing;
_needsCompositing = false;
visitChildren((RenderObject child) {
child._updateCompositingBits();
if (child.needsCompositing)
_needsCompositing = true;
});
if (isRepaintBoundary || alwaysNeedsCompositing)
_needsCompositing = true;
if (oldNeedsCompositing != _needsCompositing)
markNeedsPaint();
_needsCompositingBitsUpdate = false;
}
// 是否須要繪製
bool _needsPaint = true;
// 標記須要重繪
void markNeedsPaint() {
_needsPaint = true;
// 若是當前爲repaintBoundary 則通知owner須要重繪
if (isRepaintBoundary) {
if (owner != null) {
owner._nodesNeedingPaint.add(this);
owner.requestVisualUpdate();
}
}
// 若是當前不爲root節點 則讓parent判斷
else if (parent is RenderObject) {
final RenderObject parent = this.parent;
parent.markNeedsPaint();
}
// 若當前爲root節點 則直接通知owner重繪
else {
if (owner != null)
owner.requestVisualUpdate();
}
}
// 根節點初始化 見[RenderView]
void scheduleInitialPaint(ContainerLayer rootLayer) {
_layer = rootLayer;
owner._nodesNeedingPaint.add(this);
}
// 替換rootLayer 只有root節點纔會調用
// 當設備的像素比device pixel ratio變化時 可能會調用此方法
void replaceRootLayer(OffsetLayer rootLayer) {
_layer.detach();
_layer = rootLayer;
markNeedsPaint();
}
// 子類重寫此方法 在對應的offset完成真正的繪製操做
// 不可直接調用此方法 而是用markNeedsPaint標記 讓owner去處理
// 若是隻想繪製一個child 則用PaintingContext.paintChild 接口的方式去操做 避免直接操做render object
void paint(PaintingContext context, Offset offset) { }
}
複製代碼
本模塊中最重要的一點就是needsCompositing
這個變量。這個變量決定是否在新的layer
上繪製。在構造器中能夠看到,它由isRepaintBoundary
和alwaysNeedsCompositing
決定。isRepaintBoundary
這個能夠經過RepaintBoundary
包裹修改其爲true
。alwaysNeedsCompositing
能夠由子類修改。
所以咱們能夠經過使用RepaintBoundary
這個控件達到局部重繪的目的,以提升性能。
語義化即Semantics,主要是提供給讀屏軟件的接口,也是實現輔助功能的基礎,經過語義化接口可讓機器理解頁面上的內容,對於有視力障礙用戶可使用讀屏軟件來理解UI內容。除非有特殊的需求,通常接觸不到。
經過如下方法,處理命中測試結果。
// 重寫此方法處理事件
@override
void handleEvent(PointerEvent event, covariant HitTestEntry entry) { }
複製代碼
RenderObject
中還有兩個比較重要的方法,也提一下。
// 是否重裝 只在debug模式下生效 也就是開發中的hot reload效果
void reassemble() {
// 標記爲須要從新layout
markNeedsLayout();
// 標記從新設置是否使用新圖層
markNeedsCompositingBitsUpdate();
// 標記須要重繪
markNeedsPaint();
// 標記語義化須要更新
markNeedsSemanticsUpdate();
// 遍歷child的reassemble方法 所有標記一遍
visitChildren((RenderObject child) {
child.reassemble();
});
}
// 是否顯示在屏幕上 viewport中會使用到
void showOnScreen({
RenderObject descendant,
Rect rect,
Duration duration = Duration.zero,
Curve curve = Curves.ease,
}) {
if (parent is RenderObject) {
final RenderObject renderParent = parent;
renderParent.showOnScreen(
descendant: descendant ?? this,
rect: rect,
duration: duration,
curve: curve,
);
}
}
複製代碼
The pipeline owner manages the rendering pipeline.
整個渲染流程的管理者,具體表如今以下幾個方法:
遍歷須要relayout的render object節點,調用_layoutWithoutResize()
重寫計算佈局
遍歷須要CompositingBitsUpdate的節點,調用_updateCompositingBits()
方法更新needsCompositing
。一般在flushLayout
和flushPaint
之間執行。
遍歷須要repaint的節點,經過PaintingContext
調用子render object
的_paintWithContext
方法觸發paint
繪製。
更新語義化
RenderBox
是RenderObject
的子類,是在2D笛卡爾座標系下對RenderObject
的進一步封裝。它主要封裝了以下幾個功能點:
BoxParentData
只有offset
屬性 默認爲Offset.zero
BoxConstraints
做爲其約束size
記錄其大小RenderObjectWithChildMixin
單個child的實現->SingleChildRenderObjectWidget
ContainerRenderObjectMixin
多個children的實現->MultiChildRenderObjectWidget
開發App在笛卡爾座標系上進行繪製,因此繼承RenderBox
就能夠知足大部分場景了。
本節主要記錄了RenderObject
主要的功能和方法,理解這些內容能夠幫助咱們更好的理解Flutter UI底層原理,對咱們實現自定義的控件也有幫助。至於具體如何根據佈局繪製到屏幕上,後文會使用實例分析。
搞懂原理真是傷頭髮啊!