从源码看flutter(三):RenderObject篇


从源码看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 XbjectRenderObject 的子类。具体的阐明,能够看这儿

这儿简略的对 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 YderObjectWidgetcreateRenderObject(...) 办法创立了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 存储在子节点中时才会用到,比方 StackPosition,其间 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 #后会调用 RenderObjectElementinsertChildRenderObject(...) 将ch[ w r M ] $ h z aild刺进,这是一个笼统办法,具体的逻辑都交由子类去完结

咱们知道,比较常用的两个 RenderObjectElement 的完结类,分别是 SingleChildRen# C p z ] 4 h PderObjectElementMultiChildRenderObjectElement。前者表明只有一个子节点,常见的对应 WidgetPaddin? H A y ` h /gAlignSizeBox 等 ;后者表明有多个子节点,常见对应的 WidgetWrapStackViewport

每个 RenderObjectElement 的完结类,其 insertChildRenderObject(...) 都有所不同,但最终都会调用到 RenderObjectadoptChild(child) 办法

接下来d L 5 m H $,咱们进入到本篇文章主角 RenderObject 的相关信息

RenderObject

abstract class RenderObj- x d L F Nect extends AbstractNode with DiagnosticableTreeMixin implements HitTestTarget {
...
}

能够看到, RenderObjectAbstractNode 的子类,并且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 的改写流程,会走到 WidgetsBindingdrawFrame() 办法

  @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(),它会来到 RendererBindingdrawFrame() 办法中

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()[ ) ; AonLayout(...) 办法

咱们能够简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 hF b l J N } – true 时,便是将需要制作的y b 2 0 Q ReW + } T 7 J !nderObject 放入 PipelineOwner 保护的另一个列表7 d u I W _nodesNeedingPaint

PipelineOwnerflushO ~ 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 iReM { } ^ 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 w ParentData 目标,假如有父节点需要将某些数据存储在子节点,会给子节点设置 ParentData 并将数据存入其间。比方 RenderAligningShiftedBoxperformLaR } ] } g & K K byout() 时,就会给 child 设置_ n j G H BoxParentData,在其间存储偏移量 Offeset,代表 Widgett 便是 Center
  • RenderObject 在进行制作时,会判别当时的 isRepaX B | w b C :intBoundary 是否为 true,是则创立一个自己的 Layer 去进行进行制作,默认为 OffsetLayer;不是则从父节点中获取 Layer ,与父节点一同制作。关于 Layer 的更L / l g *多信息,将鄙人一篇中具体阐明n S K G ~ !

发表评论

提供最优质的资源集合

立即查看 了解详情