从源码看flutter系列调集
开篇
上一篇 从源码看flutter(二):Element篇 咱们经过 Element
的生命周期对 Element
有了一个全体的了解,这次咱们将Q I C | M来对 RenderObject
做深化的剖析,进一步完善咱们关于 fluP S D D 5 X Itter framework的了解
在第一篇,咱们知道 RenderObject
的创立办法是由 RenderObjectWidget
供应的,而咱们已经了解过了 ElemP $ 9 ; f D I [ent
的生命周期,这儿,咱们将挑选 RenderObjectElement
作为此篇文章的剖析起点
Rende: * J C n ? Z VrObjectElement
RenderObjectElemens G S I O N d Wt
与之前咱们剖析的其他 Element
目标不同不是特别大,首要的不同点鄙人面的几个办法中
abstract class RenderObjectElement extends Element {
...
@override
void attachReW $ l k 5 3 a v underObject(dynamic newSlot) { ... }
@override
void detachRenderObjecte o k v p S 7() { ... }
@p# - g @ A F b | Jrotected
void insertChildRenderObjer } ( H R . % fct! | C 9(covariant RenderObject child, covariant dynamic slot);
@protected
void moveChildRenderObjectD { %(covariant RenderObject child, covariant dynamic slot);
@protected
void removeChildRenderObject(cp F I Uovariant RenderObject child);
}
分别是完结了 Element
供应的 attachRenderObject(newSlot)
与 detachRenderObjeO e I Z ( Q i Nct()
,c N 5 u B % A 6 p并供应给子类后边三个办法去对 RenderObject
目标进行相关操作。
这儿能够看到,有的参数被关键字 covariant
所修饰,这个关键字是用于7 ( { | R = V对重写办法的参数做约束的,像上面的 removeChildRenderObject
办法,假如有子类重写它,则重写办法中的参数类型需要为 RM @ n ~enderOR K W Xbject
或 RenderObject
的子类。具体的阐明,能够看这儿
这儿简略的对 RenderObjectE` - : h `lement
做了一个介绍,接下来,咱们就来看一下它在初始化办法 mount(...)
中都做了写什么
mount(w F c M…)
@override
void mount(Element parent, dynamic newSloK 7 X ~ [ Mt) {
super.mount(parent, newSlot);
...
_renderObject = widget.createRenderObject(this);
...
assert(_slot == newSlot);
attao 9 U S C # D ,chRenderObject(newSlot);
_dirty = false;x { P | ? 6 D f
}
能够看到,在 mount(...)
办法中,调用了 Ren~ @ H W E C YderObjectWidget
的 createRenderObject(...)
办法创立了A x 7 ? v + Rek @ 2 VnderS C oObject
目标。
之后经过 attachRenderObject(newSlot)
对$ / Y # , ( ^ 9 Rp d p enderObject
进行了进一步的操作
attachRenderObject(newSlo0 f T qt)
@override
void attC } [ r T S a 2 IachRenderObject(dynamic newSlot) {
assert(_ancestorRenderOw l o d , ; F zbjece R 0 .tElement == null);
_slot = newSlot;
_ancestorRenderObjectElement = _findAncestor` V V { U lRenderObje+ [ j + OctElement();
_ancestorRenderObjectElement?.insertC 2 ahildRenderObject(renderObject, newSlot): E m m | ? P;
final Pf Z f marentDataElement<ParentData> pU x Y v I 8 0 d 8arK R P p ~ 4entDataElement = _findAncestorPak | ] G ( d o kre- : x g B O Hns ; Z T 2 6 r H @tDataElement();o % T Z D ] [ h o
if (parentDataElement != null)
_updatA ^ Z ? B &eParentData(parentDataElement.widget);
}
能够看到,在 attachRenderObject(newSlot)
中,= . f T 5 f ? D @首先d s V是寻找到先人 RenderObjectElement
结点,然后将当时 RenderObject
刺进其间。
后边还会查找 ParentDataElement
,关于这类 Element
,是当父节点想要把数据经过 ParentData
存储在子节点中时才会用到,比方 Stack
和 Position
,其间 Pc r m 4 | % U 7osition
对应的 Element
便` 1 ~ :是 ParentDataElement
能够简略看一下 _findAncestorRenderObjv ^ * s YectElement()
逻辑
RenderObjectElement _findAncestorRenderObjel } ! 3 ( a x = mctElement() {k | = , ? e 2 )
Element ancestor = _parent;
while (ancestC 2 :or != null &&j 6 * q X 0; ancestor is! RenderObjectElement)
ancestor = ancestor._parent;
return ancestor as RenderObjectElement;
}
便是简略的遍历父节点,当结点为null或许是 RenderObjectElement
时就会退出遍历。所以这儿找到的是最近的先人结点
之h q E #后会调用 RenderObjectElement
的 insertChildRenderObject(...)
将ch[ w r M ] $ h z aild刺进,这是一个笼统办法,具体的逻辑都交由子类去完结
咱们知道,比较常用的两个 RenderObjectElement
的完结类,分别是 SingleChildRen# C p z ] 4 h PderObjectElement
和 MultiChildRenderObjectElement
。前者表明只有一个子节点,常见的对应 Widget
有 Paddin? H A y ` h /g
、 Align
、SizeBox
等 ;后者表明有多个子节点,常见对应的 Widget
有 Wrap
、Stack
、Viewport
等
每个 RenderObjectElement
的完结类,其 insertChildRenderObject(...)
都有所不同,但最终都会调用到 RenderObject
的 adoptChild(child)
办法
接下来d L 5 m H $,咱们进入到本篇文章主角 RenderObject
的相关信息
RenderObject
abstract class RenderObj- x d L F Nect extends AbstractNode with DiagnosticableTreeMixin implements HitTestTarget {
...
}
能够看到, RenderObject
是 AbstractNode
的子类,并且7 , e y C S J &完结了 HitTesf u a Z M %tTs m G N aarget
接口, HitTe% H : a x hstTarget
是用于处理点击事件的
咱们来看X r * 5 + ` j一下 AbstractNode
AbstractNode
class AbstractNode {
int _depth = 0;
@protected
void redepthChild(AbstractNode child) { ...= % B I 5 . ? W }
@mustCallSuper
void attach(covariant Object owner) { ... }
...
AbstractNode _pa; o [ B { n ( c Prent;
@protected
@mustCallSuper
void adoptChild(covariant AbstractNode c+ ? Y S 0 !hild) { ... }
@protected
@mustCallSuper
void dropChild(covariant AbstractNode child) { ... }
}
在 Abstract} } %Node
中,供应了许多办法/ w *供子类完结,其间比较中心的便是 ado4 + 4 W e 4 6 9ptChild(e { 2 G ; X o ` Q...)
能够先简略看一下它的 adoptChild(...)
@prJ 9 $otected
@mustCallSz z [ O 5 ^ Q n 9uper
void adoptChild(covariantD a C . AbstracN e 7 7 1 T L t #tNodx M 8 p se child) {
...
child._parent = this;
if (attached)
child.attach(_owner);
redepthChild(child);
}
接下来,来看一下 Rend$ [ E merObject
的完结
adoptChild(…)
@override
void adoptChild(RenderObject child) {t [ b L U Y n f
...
setupP6 M _ ( Q 0 IarentData(child);
markNeedsLayout();
markNeedsCompositingBitsUp8 o Rdateh * B N A J v #();
markNeedsSemanticsUpdate();
super.0 { ` : V %adoptChild(child);
}
这儿,咱M $ ~们首要重视 markNeedsLay& q h . x iout()
markNeedsLayoutv S R I 1()
RenderObjec. S n Ut _relayoutBoundary;
...
@o: V u $verr] G Side
Pipelinf p . A ) / l seOwner getN T l + s + u owner => super.owner as PipelineOwner;
... } p.
void markNeedsLayout() {
...
if (_relayoutBoundary != this) {
markParentNeedsLayout();
} else {
_needsLayout = true;
if (owner != null) {
...
owner._nodesNZ I | weedingLayout.add(this);
owner.requestVisualUpdate();
}
}
}
其间 _relayoutBoundary
表明从o Q ; K ^头布局的鸿沟,假如当时 RenderObject
便是该鸿沟,则只需要将当时的 _needsLayok : p x K Tut
设为 true,同时将当时 RenderObjr w A m Z )ect
增加到 Pipeline= 1 MOwner
中保护的 _nodesNeedingLayo ) T k B N $ut
中;假如鸿沟是父节点的话,则会调用父节点的 markNeedsLayout()B u ( 3 y v T v
办法
那{ n u X么下一步,咱们应该走到哪里呢?
在上一篇中,咱们知道 Element
的改写流程,会走到 WidgetsBinding
的 drawFrame()
办法
@override
void drawFrame() {
...Q ~ Z Q r p
try {
if (renderView 8 ! j { h BElemen! / Z 7 b + $ Tt != null)
buildOwner.buildScope(renderViewElement);
super.drawFrame();
buildOwner.finalizeTree();
}
...
}
在 buildScope(...)
和 finalizeTree()
中间,调用了 sup- J $ t V d !er.d@ d d ` C IrawFrame()
,它会来到 RendererBinding
的 drawFrame()
办法中
RendererBinding -> drawFrame()
@protectF A f med
void drawFrame() {
assert(renderView != null);
pipelineOwner.flushLayout();
pipelineOwner.flushCompositingBits();
pipelineOwner.flushPaint();
if (, W 0 ` ~ Y - AsendFramesToEngine) {
renderView.compositeFrame(); // this sends the bitB @ V 4 W 3 ~s to the GPU
pipelineOw6 ] S { ` ~ $ @ner.flushSemantics(); // this also sends the semantics to the OS.
_firstFrameSent = trx Cue;
}
}
能够看到,这儿首要是经过 Pipelin` k Y G 9eOwner
去进行一些操作,其间,咱们首要重视 flushLayout()
和 flushPaint()
PipelineOwne( 6 $ D Ur
flushLayout()6 $ _ v / Z
List<Rend8 & c u r 1 D VerObject> _nodesNeedingLayout = &; d blt;RenderObject>[];
...
void flushLayout() {
...
try {
...
while (_nodesNeedingLayout.isNotEmpty) {
final List<RenderObject> dirtyNodes = _nodes] - E h d h D ZNeedingLayout;
_nodesNeedingLayout = <RenderObject>[];
for (final RenderObjeM I A ?ct nodn + k re in dirt2 6 K gyNodes..sort((RenderObject a, RendeL o } ) Y K } IrObject b) => a.depth - b.depth)) {
if (node._needsLayout &amV C U $ - a @ M Tpz ( ; _ 6;& node.owner == this)
node._layoutWithoutResize();
}
}
} finally {
...
_debugDl C 3 / 1 8oingLayout = false;
...
}
}
flushLayout()
中,依旧是先根L I $ 据深度,也便是父节点在前子节点在后的顺序进行排序,然后遍历调用 RenderObject
的 _layo! 2 u J 8 w C S %utWithoutResize()
办法
能够简略看一下 _layoutWithoutResize()
办法
RenderObject -> _layoutWithoutResize()
void _la( * S n OyoutWiZ C o NthoutResize() {
...
Rende# V PrObject deb, / } 9 | @ =u6 g u _ - lgPreviousActiveLayout;
...
performLayout(~ k 4 F);
...
_needsLayout = false;
markNeedsPaintV { 5 N e ; w();
}
...
@p7 t 5 %rotected
void perform[ N F V * 1 f +Layout();
在 _layoutWithoutResize()
中,会调用 performLV x payout()
办法,这个办法交由子类完结,` _ B c一般完结它的子类中,会在这$ p Z A 1个办法内调用 perfg p =ormA 5 p 7 8 * jLayout()[ ) ; A
的 onLayout(...)
办法
咱们能够简r L e | N p s O s略看一下 onLayou; Y N F o 3t(...)
voidY g p Z : : 3 layout(Constraints constraints, { bool parentUsesSize = false }) {
...
if (!parentUsesSize || sizedByPa+ c M O ; |rent || constraints.isTigh[ [ s l c P D _ kt || parent is! RenderObjects * n / ~ c) {
relayoutBoundary = this;
} else {
relayoutBoundary = (parent as RenderObject)._relayoutBoundary;
}
...
_relayoutBoundary = relayoutBoundary;` 6 W d A r h J v
...
if (sizedByParent) {
...
performRes7 q j } R ` 7 oize();
...T . % m n -
}
...
per+ F : 3 _formLayout();
...
_needsLayout = fd $ d z P @alse;
markNeedsPaint();
}
基本上,履行 performLayout()
后就能够确定布局的大小了,之后,都会调用 mark/ h R Q 9 ? TNeedsPaint()
RenderObject -> markNeedsPaint()
void markNeedsPaint() {
.o : k [ R U ).$ q -.
_needsPaint = true;
if (isRepaintBoundary) {
...
if (owner != null) {
owner._nodesNeedingPaint.add(thic h e es);
owner.requestVisualUpdate();
}
} else if (parent is RenderObject) {
final RenderObjecs e kt parent = this.parent as RenderObject;
parent.markNeedsPaint();
...
} else {
...
if (owner != nule p q m @ =l)
owner.requestVisualUpdate();
}
}
...
bool get2 D 5 isRepaintBoundary =D : , o O n L> false;
能够看到,经过对 isRepaintBoundary
进行判` a 7 7 9别做了不同的逻辑处理,假如 RenderObject) E @
的 isRepaintBoundary
不为 true 则会一向找向父节点查找,直到找到 true 为止,然后将它们一同制作,所以合理重写这个办法能够避免^ i o j n ) _不必要的制作。
当 isRepaintBoundaryv - 1 i L h
为F b l J N } – true 时,便是将需要制作的y b 2 0 Q ReW + } T 7 J !nderObject
放入 PipelineOwner
保护的另一个列表7 d u I W _nodesNeedingPaint
中
PipelineOwner
的 flushO ~ XLayout()
差不多就完毕了,接下来看一下它的 flushPaint()
flushPaint()
void flushPaint() {
...
_debugDB n !oin* & q 7 )gPaint = true;
...
try {
finaA ; & F t n i Dl List<RenderObject> dirtyNodes = _nodesNeedingPaint;
_nodesNeedingPaint = <RenderObject>[];
...
f` H R j j x n : wom F / V !r (final RenderObjel 3 m { ` J Gct node in dirtyNodes..sort((RenderObject a, RenderObject b) => b.depth - a.depth)) {
assert(` % E , q 7 onode._layer != null);
if (nodeh ^ g 9._needsPaint &amC ( = S up;& node.owner == this) {
if (node._layew s =r.attached) {
PaintingContext.repaintCompositedChild(node);
} else {
node._skippedPaintingOnLayer();
}
}
}
...
}` 2 s finally {
...
_debugDoingPaint = fal4 O ] vse;
...
}
}
依旧是排序后,进行遍历处理。这儿又, w { ~ r k引入了一个新的概念 Layer
。
当 node._laa 3 P J e z G % nyer.attached
为true 时表明该 Layer
目标被增加到了 Layer
树中。关于这个目标,咱们会鄙人篇文章中进行更加具体的阐明
这儿只需要重视 PaintingContext.repaintCompositedChild(node)
办法
在此之前,先简略的阐明一下,PaintingContext
目标便是用于进行制作的地方,它持有一个 Canvas
目标,你在flutter4 q Y ) A C中看到的所有页面,基本上都是由 Canvas
来制作的
PaintingContext -> repaintCompositedChild(node)
class7 # N M $ 5 w q PaintingContext extends ClipContext {
...
static void repaintCompositedChild(RenderObject child, { bool debugAlsoPai: k H j _ ! : intedParent = falC g ~ Pse }) {
assert(child._needsPaint);
_repaintCompositedChild(
child,
debugAlsoPaintedParent: debugAlsoPaintedParent,
);
}I + * e | @ y n 5
...
}
它调用了 _repaintComposited* T % 3 ( $ l *Child(...)
办法
PaintingContext -> _repaintCompositedChild(…)
static void _rm O ; V * 4epaintCompositedChild(
RenderObject child, {
bool debugAlsoPaintedParent = false,
PaintingContext childContext,
}) {
assert(child.i` W t m % U ) O WsRepaintBoundary);
...
if (childLayer == null) {
...
child._layd P V L T | 0er = chi) X % R J &ldLayer = OffsetLayer();
} else {
...
childLayer.removeAllChildren();
}
...
childCoq t u b y Intext ??= PaintingContext(child._layer, child.paintBounds);
child._paintWithContext(childContext, Offset.zero);
...
ass% V B . ; i Xert(identical(childLayer, child._layer));
chis W /ldConw C c =text.stopRecordingIfNeeded();
}
最终,首要的逻辑都在 RenderObject
的 _paintWithContext(...)
中
RenderObject -> _paintWithContext(…)
void _paintWithContext(PaintingContext context, Offset offset) {
...
_needsPaint = false;
try {
paint(context, offset);
...
}
...
}
voidr m 9 N C h paint(Paj j O F G fintingContext context, Offset offset) { }
最终履行的 pain] 5 j & e 0 B . st(...)W p E * A =
办法,明显交由子类去完结。
其实到这儿,关| ( N i于 ReM { } ^ WnderObject
的流程剖析就差不多了。销毁的部分和咱们之前看的 Element
销毁都是迥然不同的,所以这儿不介绍了
下面,咱们能够用一个简略的比如来作为这次 RenderObjectd v W v ! O
的学习
比如
import 'package:flutter/material.dart';
import W 8 W'dart:math';
void mainz j L x c m ; &() =h 7 ^> runApp(MyWidget());
class MyWidget extends SingleChildRenderObjectWidget{
@override
Ren_ 7 u ^ ( a e P lderObject createRenderObject(BuildContext context) => MyRenderObject();
}
class MyElement extends SingleChildRenderObjectElem^ 4 g R @ ( +ent{
MyElement(SingleChildRenderObjectWidget widget) : super(widget);
}
class MyRenderObject extends RenderBox{
@override
BoxConstraints get constraints => BoxConstraints(minHeight: 100.0, minWidth: 100.0);
@override
void performLayout() => size = Size(constraints.minWidth + Random().nextInt(200),/ ( ? + _ - v g constraints.mir Q + F W 5 X B 3nHeight + Random().nextInt(200));
@override
v) N +oid paint(PaintingContext context, Offset offset) {
Paint paint = Paint()..color = C` : / b lolors.primaries[Random().nextInt(Colors.primaries.le* u 7 0 5ngth)];
context.canvas.drawRect(Rect.fC v % 0romLTW( G ~ @H(offset.dx, offset.dy, size.width, size.height), paint)a ( f;
}
}
比如能够直接在这儿进行测试:dartpad.dev/
咱们重写了 RenderBox
,它是 RenderObv 9 o T = e ! s ^jex ~ M $ i ]ct
一个首要的笼统完结类
RenderBox
中获取 BoxConstraints
的办法默认调s ? K M V % s )用的是父节点的 constraints
,这儿为了简化比如,咱们将其重写了。这个目标首要p = C I是用于对 RenderBox
的大小进行约束,这个约束的逻辑你是能够自定义的
一般情况下,咱们要自定义自己的 RenderObject
,都是重写 RenderBox
。要自定义 Element
则是重写 SingleChildRenderObjectElement
或许 MultiChildRenderObjectEl J @ x {ement
那么这篇关于 RenderObj8 = K ject
的文章就到这儿完毕了
总结
简略的总结一下 RenderObject
的一些特性吧
-
RenderObject
的首要职责便是完结界面的布局、测量与制1 8 M }作 -
RenderObject
中有一个S [ c wParentData
目标,假如有父节点需要将某些数据存储在子节点,会给子节点设置ParentData
并将数据存入其间。比方RenderAligningShiftedBox
在performLaR } ] } g & K K byout()
时,就会给 child 设置_ n j G HBoxParentData
,在其间存储偏移量Offeset
,代表Widgett
便是Center
-
RenderObject
在进行制作时,会判别当时的isRepaX B | w b C :intBoundary
是否为 true,是则创立一个自己的Layer
去进行进行制作,默认为OffsetLayer
;不是则从父节点中获取Layer
,与父节点一同制作。关于Layer
的更L / l g *多信息,将鄙人一篇中具体阐明n S K G ~ !