小菜前段時間簡單了解了一下 Widget 和 Element,其中 Widget 主要是存放渲染内容以及布局資訊等,僅作為一個資訊存儲的容器;Element 主要用于存放上下文環境,周遊 UI View 視圖樹;而小菜今天嘗試學習的 RenderObject 才是 UI View 真正的渲染部分;
RenderObject
RenderObject 作為渲染樹中的一個對象;其 layout() 和 paint() 是渲染庫核心,負責管理布局和渲染等;RenderObject 定義了布局繪制協定,但并沒定義具體布局繪制模型;
源碼分析
RenderObject 可以從多個次元研究,可以通過 layout() 和 paint() 對比 Android 的繪制流程,也可以根據其屬性和互動的對象(parent / owner / child)來學習;小菜從頭開始為了盡可能多的了解源碼,嘗試第二種方式進一步學習;
abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin implements HitTestTarget {
AbstractNode _rootNode;
ParentData parentData;
Constraints _constraints;
@protected
Constraints get constraints => _constraints;
PipelineOwner _owner;
bool get attached => _owner != null;
void setupParentData(covariant RenderObject child) {}
void adoptChild(RenderObject child) {}
void dropChild(RenderObject child) {}
void attach(PipelineOwner owner) {}
void detach() {}
}
parent 相關
1. ParentData
ParentData parentData;
void setupParentData(covariant RenderObject child) {
assert(_debugCanPerformMutations);
if (child.parentData is! ParentData)
child.parentData = ParentData();
}
RenderObject 包括兩個重要屬性 parent 和 ParentData 插槽;ParentData 做為一個預留的變量,由 parent 指派,傳遞資訊給 child 的存儲容器;通常所有和 child 特定的資料都可以存儲在 ParentData 中;
2. Constraints
void layout(Constraints constraints, { bool parentUsesSize = false }) {
RenderObject relayoutBoundary;
if (!parentUsesSize || sizedByParent || constraints.isTight || parent is! RenderObject) {
relayoutBoundary = this;
} else {
final RenderObject parent = this.parent;
relayoutBoundary = parent._relayoutBoundary;
}
if (!_needsLayout && constraints == _constraints && relayoutBoundary == _relayoutBoundary) return;
_constraints = constraints;
if (_relayoutBoundary != null && relayoutBoundary != _relayoutBoundary) {
visitChildren((RenderObject child) {
child._cleanRelayoutBoundary();
});
}
_relayoutBoundary = relayoutBoundary;
if (sizedByParent) {
try {
performResize();
} catch (e, stack) {
_debugReportException('performResize', e, stack);
}
}
RenderObject debugPreviousActiveLayout;
try {
performLayout();
markNeedsSemanticsUpdate();
} catch (e, stack) {
_debugReportException('performLayout', e, stack);
}
_needsLayout = false;
markNeedsPaint();
}
Constraints 作為 RenderObject 中 parent 和 child 之間的布局限制;layout() 作為 RenderObject 的核心方法,需要傳入 Constraints 作為限制,配合 parentUsesSize 判斷 RenderObject 在 child 子節點發生變化時,parent 父節點是否需要重新繪制;
3. relayoutBoundary
RenderObject relayoutBoundary;
if (!parentUsesSize || sizedByParent || constraints.isTight || parent is! RenderObject) {
relayoutBoundary = this;
} else {
final RenderObject parent = this.parent;
relayoutBoundary = parent._relayoutBoundary;
}
if (_relayoutBoundary != null && relayoutBoundary != _relayoutBoundary) {
visitChildren((RenderObject child) {
child._cleanRelayoutBoundary();
});
}
void markNeedsLayout() {
if (_needsLayout) {
return;
}
if (_relayoutBoundary != this) {
markParentNeedsLayout();
} else {
_needsLayout = true;
if (owner != null) {
owner._nodesNeedingLayout.add(this);
owner.requestVisualUpdate();
}
}
}
layout() 中定義了一個 RenderObject 類型的 relayoutBoundary 布局邊界,如果布局邊界發生變化,則周遊清空所有已記錄的邊界并重新設定;
markNeedsLayout() 中也需要進行布局邊界判斷,若 RenderObject 自身不是 relayoutBoundary,則向 parent 父節點查找,直到找到确定是 relayoutBoundary 的 RenderObject 并标記為 dirty;
layout() 确定自己是否為邊界需要判斷四個條件,分别是 !parentUsesSize parent 父節點是否關心自己的大小;sizedByParent 是否由 parent 父節點判斷大小;constraints.isTight 是否嚴格限制;parent is! RenderObject 自身是否為 root 根節點;
owner 相關
PipelineOwner 作為整個渲染流程的管理者;提供用于驅動渲染管道的接口,并存儲在管道的每個階段中已請求通路渲染對象的狀态等;
1. flushLayout
void flushLayout() {
if (!kReleaseMode) {
Timeline.startSync('Layout', arguments: timelineWhitelistArguments);
}
try {
while (_nodesNeedingLayout.isNotEmpty) {
final List<RenderObject> dirtyNodes = _nodesNeedingLayout;
_nodesNeedingLayout = <RenderObject>[];
for (RenderObject node in dirtyNodes..sort((RenderObject a, RenderObject b) => a.depth - b.depth)) {
if (node._needsLayout && node.owner == this)
node._layoutWithoutResize();
}
}
} finally {
if (!kReleaseMode) Timeline.finishSync();
}
}
flushLayout() 用于周遊所有标記為 dirty 的需要重新布局的 RenderObjects 并重新計算其布局尺寸和位置等;
2. flushCompositingBits
void flushCompositingBits() {
if (!kReleaseMode) {
Timeline.startSync('Compositing bits');
}
_nodesNeedingCompositingBitsUpdate.sort((RenderObject a, RenderObject b) => a.depth - b.depth);
for (RenderObject node in _nodesNeedingCompositingBitsUpdate) {
if (node._needsCompositingBitsUpdate && node.owner == this)
node._updateCompositingBits();
}
_nodesNeedingCompositingBitsUpdate.clear();
if (!kReleaseMode) {
Timeline.finishSync();
}
}
flushCompositingBits() 用于周遊所有标記為 dirty 的需要 CompositingBitsUpdate 合并更新的子節點,再次階段,每個 RenderObject 都會了解其子節點是否需要合并更新;
3. flushPaint
void flushPaint() {
if (!kReleaseMode) {
Timeline.startSync('Paint', arguments: timelineWhitelistArguments);
}
try {
final List<RenderObject> dirtyNodes = _nodesNeedingPaint;
_nodesNeedingPaint = <RenderObject>[];
for (RenderObject node in dirtyNodes..sort((RenderObject a, RenderObject b) => b.depth - a.depth)) {
if (node._needsPaint && node.owner == this) {
if (node._layer.attached) {
PaintingContext.repaintCompositedChild(node);
} else { node._skippedPaintingOnLayer(); }
}
}
} finally {
if (!kReleaseMode) { Timeline.finishSync(); }
}
}
flushPaint() 用于周遊所有标記為 dirty 的需要重新繪制的子節點,并生成 Layer 用于繪制展示;
4. attach / detach
@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();
}
}
layout() 中在 attach() 和 detach() 中也需要 PipelineOwner;attach() 主要通知管理者 owner 将其插入到渲染樹中标記需要計算布局 layout 并調用 markNeedsPaint 重新繪制;detach() 主要是通知管理者取消關聯;
child 相關
對于 child 子節點,小菜主要學習如下三個方法;
1. adoptChild
@override
void adoptChild(RenderObject child) {
setupParentData(child);
markNeedsLayout();
markNeedsCompositingBitsUpdate();
markNeedsSemanticsUpdate();
super.adoptChild(child);
}
adoptChild() 主要是 RenderObject 添加一個 child 子節點;其中需要通過 setupParentData() 來擷取 ParentData 中的資料并更新;
2. dropChild
@override
void dropChild(RenderObject child) {
child._cleanRelayoutBoundary();
child.parentData.detach();
child.parentData = null;
super.dropChild(child);
markNeedsLayout();
markNeedsCompositingBitsUpdate();
markNeedsSemanticsUpdate();
}
dropChild() 是和 adoptChild() 對應的方法,主要用于 RenderObject 删除一個 child 子節點;删除過程中需要 _cleanRelayoutBoundary 清除邊界并删除 ParentData,之後再更新;
3. paintChild
void paintChild(RenderObject child, Offset offset) {
if (child.isRepaintBoundary) {
stopRecordingIfNeeded();
_compositeChild(child, offset);
} else {
child._paintWithContext(this, offset);
}
}
paintChild() 為繪制一個子節點的 RenderObject;如果該子節點有自己合成層,則 child 子節點将被合成到與此繪制相關的上下文相關的 Layer 層中;
RenderBox
RenderObject 并沒定義具體布局繪制模型,是以小菜簡單學習了一下 RenderBox;RenderBox 是 RenderObject 的子類,以螢幕左上角為原點(包括頂部狀态欄)坐标系;BoxParentData 作為 child 子節點傳輸資料,BoxConstraints 作為其限制條件,通過 Size 記錄其尺寸大小;可以定義具體的布局繪制模型;
RenderObject 涉及的方式方法較多,小菜對于源碼的了解還不夠深入,如有錯誤,請多多指導!
來源: 阿策小和尚