Flutter的绘制流程

    index += 1;
    //当_dirtyElements集合中的"脏"节点还未处理完毕时,又添加了新的"脏"节点
    if (dirtyCount < _dirtyElements.length || _dirtyElementsNeedsResorting) {
      //根据"脏"节点的深度进行排序
      _dirtyElements.sort(Element._sort);
      _dirtyElementsNeedsResorting = false;
      dirtyCount = _dirtyElements.length;
      //如果当前节点的深度比新加入的"脏"节点深度要深,则需要将处理坐标指向新加入的"脏"节点
      while (index > 0 && _dirtyElements[index - 1].dirty) {
        index -= 1;
      }
    }
  }
} finally {
  //清除_dirtyElements中所有节点的"脏"状态
  for (Element element in _dirtyElements) {
    element._inDirtyList = false;
  }
  _dirtyElements.clear();
  _scheduledFlushDirtyElements = false;
  _dirtyElementsNeedsResorting = null;
  Timeline.finishSync();
}

}

\_dirtyElements是一个集合,存储了所有标记为"脏"的节点。在对其中的"脏"节点进行处理时,需要首先对集合中的"脏"节点进行排序,其排序规则如下。


● 如果"脏"节点的深度不同,则按照深度进行升序排序


● 如果"脏"节点的深度相同,则会将"脏"节点放在集合的右侧,"干净"节点则在在集合的左侧。


在排序完成后,就要遍历该集合,对其中的"脏"节点进行处理。在这里调用的是rebuild函数,通过该函数,会重新创建"脏"节点下的所有Widget对象,并根据新的Widget对象来判断是否需要重用Element对象。一般只要不是增删Widget,Element对象都会被重用,从而也就会重用RenderObject对象。由于Widget是一个非常轻量级的数据结构,所以在UI更新时做到了把性能损耗降到最低。


如果\_dirtyElements中的"脏"节点还未处理完毕,就又新增了"脏"节点,那么这时候就会重新排序,保证\_dirtyElements集合的左侧永远是"干净"节点,右侧永远是"脏"节点。


由于rebuild函数比较重要,这里就重点介绍一下该函数,在rebuild函数中会调用performRebuild函数,该函数是一个抽象函数,在其子类实现,而标记为"脏"的Element都是StatefulElement。所以就来StatefulElement或者其父类中查找performRebuild函数。

abstract class ComponentElement extends Element {

...

@override

void performRebuild() {

Widget built;

try {

//重新创建新的Widget对象

built = build();

debugWidgetBuilderValue(widget, built);

} catch (e, stack) {

//当构建Widget对象出错时展示的默认页面,可以修改该页面来使异常界面更友好的显示

built = ErrorWidget.builder(_debugReportException('building $this', e, stack));

} finally {

//清除"脏"标记

_dirty = false;

}

try {

//更新子Element对应的Widget对象

_child = updateChild(_child, built, slot);

assert(_child != null);

} catch (e, stack) {

//当构建Widget对象出错时展示的默认页面

built = ErrorWidget.builder(_debugReportException('building $this', e, stack));

_child = updateChild(null, built, slot);

}

}

}

performRebuild函数做的事很简单,就是创建新的Widget对象来替换旧的对象。上面的build函数调用的就是State类中的build函数,然后再调用Element的updateChild函数,就是更新Element对应的Widget对象。而在updateChild函数中又会调用子Element的update函数,从而调用子Element的performRebuild,然后在调用子Element的updateChild、update函数。以此类推,从而更新其所有子Element的Widget对象。


![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://img-home.csdnimg.cn/images/20230724024159.png?origin_url=https%3A%2F%2Fjuejin.cn%2F&pos_id=img-0yR7cvUa-1694154012759)


最后就是调用叶子节点的updateRenderObject函数来更新RenderObject。在更新RenderObject对象时,会根据情况来对需要重新布局及重新绘制的RenderObject对象进行标记。然后等待下一次的Vsync信号时来重新布局及绘制UI。


##### 2.2. 标记RenderObject:


对于RenderObject对象,可以通过markNeedsLayout及markNeedsPaint来标记是否需要重新布局及重新绘制。但在当前阶段只会调用markNeedsLayout来标记需要重新布局的RenderObject对象,在下一阶段才会标记需要重新绘制的RenderObject,所以先来看markNeedsLayout函数。

void markNeedsLayout() {

...

//判断布局边界是否是是当前RenderObject对象

if (_relayoutBoundary != this) {

markParentNeedsLayout();

} else {

_needsLayout = true;

if (owner != null) {

//标记当前RenderObject及其子RenderObject对象需要重新布局

//将当前RenderObject添加到集合中。

owner._nodesNeedingLayout.add(this);

owner.requestVisualUpdate();

}

}

}

@protected

void markParentNeedsLayout() {

_needsLayout = true;

final RenderObject parent = this.parent;

if (!_doingThisLayoutWithCallback) {

//调用父类的markNeedsLayout函数

parent.markNeedsLayout();

} else {

assert(parent._debugDoingThisLayout);

}

assert(parent == this.parent);

}

markNeedsLayout函数的代码实现很简单,就是不断遍历父RenderObject对象,从而找到布局边界的RenderObject对象,并将该RenderObject对象添加到集合\_nodesNeedingLayout中,然后在下一阶段就从该对象开始布局。


在这里有个"布局边界"的概念,在Flutter中,可以给任意节点设置布局边界,即当边界内的任何对象发生重新布局时,不会影响边界外的对象,反之亦然。


在重新构建build函数及标记RenderObject完成后,就进入下一阶段,开始布局。


#### 3. 布局阶段:


计算Widget的大小及位置的确定。


在该阶段,会确定每个组件的大小及位置,相当于Android中的onMeasure+onLayout函数所实现的功能。如果是第一次调用该函数,该阶段就会遍历所有的组件,来确定其大小及位置;否则该阶段就会遍历布局边界内的所有组件,来确定其大小及位置。


当上一阶段中的buildOwner.buildScope(renderViewElement)函数执行完毕后,就会调用RendererBinding的drawFrame函数,该函数实现非常简洁。

//绘制帧

void drawFrame() {

//对指定组件及其子组件进行大小测量及位置确定

pipelineOwner.flushLayout();

pipelineOwner.flushCompositingBits();

pipelineOwner.flushPaint();

renderView.compositeFrame();

pipelineOwner.flushSemantics(); }

其中flushLayout就是进行组件的大小及位置确定,在该函数中会遍历集合\_nodesNeedingLayout并调用集合中每个对象的\_layoutWithoutResize函数。

void flushLayout() {

try {

while (_nodesNeedingLayout.isNotEmpty) {

final List dirtyNodes = _nodesNeedingLayout;

_nodesNeedingLayout = [];

for (RenderObject node in dirtyNodes...sort((RenderObject a, RenderObject b) => a.depth - b.depth)) {

//调用RenderObject对象的_layoutWithoutResize函数

if (node._needsLayout && node.owner == this)

node._layoutWithoutResize();

}

}

} finally {...}

}

\_layoutWithoutResize函数是私有的,所以不存在重写的问题。那么就直接来看该函数。

void _layoutWithoutResize() {

try {

performLayout();

markNeedsSemanticsUpdate();

} catch (e, stack) {...}

_needsLayout = false;

markNeedsPaint();

}

\_layoutWithoutResize函数很简单,就直接调用了performLayout函数。


当然,RenderObject对象的size也不是随便确定的,因为在调用RenderObject的layout函数时,会传递一个继承自Constraints的对象。该对象是一个布局约束,由父传给子,子会根据该对象来决定自己的大小。


##### 3.1. 标记RenderObject


当大小及位置确定后,就又会对RenderObject进行一次标记,这次跟上一阶段的标记大同小异,但这次是标记可绘制的RenderObject对象,然后在后面对这些对象进行重新绘制。标记可绘制的RenderObject对象是通过markNeedsPaint函数来实现的,代码如下。

void markNeedsPaint() {

if (_needsPaint)

return;

_needsPaint = true;

if (isRepaintBoundary) {

//标记需要重新绘制的RenderObject对象

//需要绘制当前图层

if (owner != null) {

owner._nodesNeedingPaint.add(this);

owner.requestVisualUpdate();

}

} else if (parent is RenderObject) {

//没有自己的图层,与父类共用同一图层

final RenderObject parent = this.parent;

//遍历其父RenderObject对象

parent.markNeedsPaint();

} else {

//当是RenderView时,需要自己创建新的图层

if (owner != null)

owner.requestVisualUpdate();

}

}

markNeedsPaint函数中涉及到了一个"重绘边界"的概念。在进入和走出重绘边界时,Flutter会强制切换新的图层,这样就可以避免边界内外的互相影响。当然重绘边界也可以在任何节点手动设置,但是一般不需要我们来实现,Flutter提供的控件默认会在需要设置的地方自动设置。


#### 4. compositingBits阶段:


重绘之前的预处理操作,检查RenderObject是否需要重绘。


在组件的大小及位置确定后,就会进入当前阶段。该阶段主要是做一件事,就是将RenderObject树上新增及删除的RenderObject对象标记为"脏",方便在下一阶段对这些RenderObject对象进行重绘。具体代码实现是在flushCompositingBits函数中,该函数在Layout阶段后立即调用。

void flushCompositingBits() {

...

//将RenderObject对象按照深度进行排序

_nodesNeedingCompositingBitsUpdate.sort((RenderObject a, RenderObject b) => a.depth - b.depth);

for (RenderObject node in _nodesNeedingCompositingBitsUpdate) {

if (node._needsCompositingBitsUpdate && node.owner == this)

//将RenderObject对象及其子对象标记为"脏"

node._updateCompositingBits();

}

_nodesNeedingCompositingBitsUpdate.clear();

...

}

nodesNeedingCompositingBitsUpdate是一个集合,只有RenderObject对象的\_needsCompositing为true时,才会添加到该集合中。在RenderObject对象创建时,\_needsCompositing的值会根据isRepaintBoundary及alwaysNeedsCompositing来共同判断。

RenderObject() {

//isRepaintBoundary决定当前RenderObject是否与父RenderObject分开绘制,默认为false,其值在当前对象的生命周期内无法修改。也就是判断当前对象是否是绘制边界

//alwaysNeedsCompositing为true表示当前RenderObject会一直重绘,如视频播放,默认为false

_needsCompositing = isRepaintBoundary || alwaysNeedsCompositing;

}

然后在向树中添加或者删除RenderObject对象时会调用adoptChild及dropChild函数,而这两个函数都会调用markNeedsCompositingBitsUpdate函数,也就在markNeedsCompositingBitsUpdate函数内完成了将当前对象添加到集合中的操作。

//向树中添加当前节点

@override

void adoptChild(RenderObject child) {

setupParentData(child);

markNeedsLayout();

//将当前对象的_needsCompositingBitsUpdate值标为true

markNeedsCompositingBitsUpdate();

markNeedsSemanticsUpdate();

super.adoptChild(child);

}

//从树中移除当前节点

@override

void dropChild(RenderObject child) {

child._cleanRelayoutBoundary();

child.parentData.detach();

child.parentData = null;

super.dropChild(child);

markNeedsLayout();

//将当前对象的_needsCompositingBitsUpdate值标为true

markNeedsCompositingBitsUpdate();

markNeedsSemanticsUpdate();

}

//

void markNeedsCompositingBitsUpdate() {

if (_needsCompositingBitsUpdate)

return;

_needsCompositingBitsUpdate = true;

if (parent is RenderObject) {

final RenderObject parent = this.parent;

if (parent._needsCompositingBitsUpdate)

return;

if (!isRepaintBoundary && !parent.isRepaintBoundary) {

parent.markNeedsCompositingBitsUpdate();

return;

}

}

//将当前对象或者其父对象添加到_nodesNeedingCompositingBitsUpdate集合中

if (owner != null)

owner._nodesNeedingCompositingBitsUpdate.add(this);

}

这样就会在调用flushCompositingBits函数时,就会调用\_updateCompositingBits函数来判断是否将这些对象及子对象标记为"脏",然后在下一阶段进行绘制。

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;

}

#### 5. 绘制阶段:


根据Widget大小及位置来绘制UI。


通过调用flushPaint函数就可以重绘已经标记的"脏"RenderObject对象及其子对象。

void flushPaint() {

try {

final List dirtyNodes = _nodesNeedingPaint;

_nodesNeedingPaint = [];

//根据节点深度进行排序

for (RenderObject node in dirtyNodes...sort((RenderObject a, RenderObject b) => b.depth - a.depth)) {

if (node._needsPaint && node.owner == this) {

//当前对象是否与layer进行关联

if (node._layer.attached) {

//在图层上绘制UI

PaintingContext.repaintCompositedChild(node);

} else {

//跳过UI绘制,但当前节点为"脏"的状态不会改变

node._skippedPaintingOnLayer();

}

}

}

} finally {}

}

flushPaint函数中,每次遍历"脏"RenderObject对象时,都会进行一次排序,避免重复绘制。然后在判断当前对象是否与Layer进行关联,如果没有关联,则无法进行绘制,但不会清除"脏"标记。下面来看repaintCompositedChild函数的实现。

static void repaintCompositedChild(RenderObject child, { bool

_repaintCompositedChild(

child,

debugAlsoPaintedParent: debugAlsoPaintedParent,

);

}

static void _repaintCompositedChild(

RenderObject child, {

bool debugAlsoPaintedParent = false,

PaintingContext childContext,

}) {

//拿到Layer对象

OffsetLayer childLayer = child._layer;

if (childLayer == null) {

//创建新的Layer对象

child._layer = childLayer = OffsetLayer();

} else {

//移除Layer对象的后继节点

childLayer.removeAllChildren();

}

//创建context对象

childContext ??= PaintingContext(child._layer, child.paintBounds);

//调用paint函数开始绘制

child._paintWithContext(childContext, Offset.zero);

childContext.stopRecordingIfNeeded();

}

##### 5.1. Layer


在该函数中主要是对Layer对象的处理,然后调用\_paintWithContext函数,在\_paintWithContext函数中就会调用paint这个函数,从而实现UI的绘制。至此,就完成了UI的绘制,下面再来看一个被忽略的对象------Layer。


Layer是"图层"意思,在Flutter中是最容易被忽略但又无比重要的一个类。它非常贴近底层,可以很容易的看到调用Native方法。


Layer跟其他三棵树一样,也是一棵树,有"脏"状态的标记、更新等操作。不同的是,Layer是一个双链表结构,在每个Layer对象中都会指向其前置节点与后置节点(叶子Layer的后置节点为null)。

abstract class Layer extends AbstractNode with DiagnosticableTreeMixin {

//返回父节点

@override

ContainerLayer get parent => super.parent;

//当前节点状态,为true表示当前节点是"脏"数据。需要重绘

bool _needsAddToScene = true;

//将当前节点标记为"脏"

@protected

@visibleForTesting

void markNeedsAddToScene() {

// Already marked. Short-circuit.

if (_needsAddToScene) {

return;

}

_needsAddToScene = true;

}

@protected

bool get alwaysNeedsAddToScene => false;

//这个是一个非常重要的东西,主要用于节点数据的缓存。存储当前节点的渲染数据,如果当前节点不需要更新,就直接拿存储的数据使用。

@protected

ui.EngineLayer get engineLayer => _engineLayer;

//更改当前节点的数据

@protected

set engineLayer(ui.EngineLayer value) {

_engineLayer = value;

if (parent != null && !parent.alwaysNeedsAddToScene) {

//将父节点标记需要更新的状态

parent.markNeedsAddToScene();

}

}

}

ui.EngineLayer _engineLayer;

//更新当前节点状态,如果_needsAddToScene为true,则将当前节点标记为"脏"

@protected

@visibleForTesting

void updateSubtreeNeedsAddToScene() {

_needsAddToScene = _needsAddToScene || alwaysNeedsAddToScene;

}

//指向后置节点

Layer get nextSibling => _nextSibling;

Layer _nextSibling;

//指向前置节点

Layer get previousSibling => _previousSibling;

Layer _previousSibling;

//将子节点从Layer树中移除

@override

void dropChild(AbstractNode child) {

if (!alwaysNeedsAddToScene) {

markNeedsAddToScene();

}

super.dropChild(child);

}

//将当前节点添加到Layer树中

@override

void adoptChild(AbstractNode child) {

if (!alwaysNeedsAddToScene) {

markNeedsAddToScene();

}

super.adoptChild(child);

}

//将当前节点从Layer树中移除

@mustCallSuper

void remove() {

parent?._removeChild(this);

}

void addToScene(ui.SceneBuilder builder, [ Offset layerOffset = Offset.zero ]);

void _addToSceneWithRetainedRendering(ui.SceneBuilder builder) {

//使用当前节点的缓存的数据

if (!_needsAddToScene && _engineLayer != null) {

builder.addRetained(_engineLayer);

return;

}

addToScene(builder);

//将当前节点标记为"干净"的

_needsAddToScene = false;

}

}

##### 5.2. Layer节点的添加


previousSibling与nextSibling分别是Layer的前置节点与后置节点,当向Layer树中添加Layer节点时,也会将当前Layer设置为父节点的后置节点,父节点设置为当前节点的前置节点。这样,就形成了一颗树。

class ContainerLayer extends Layer {

...

//将当前节点及其链表上的所有子节点都加入到Layer树中

@override

void attach(Object owner) {

super.attach(owner);

Layer child = firstChild;

while (child != null) {

child.attach(owner);

child = child.nextSibling;

}

}

//将当前节点及其链表上的所有子节点都从Layer树中移除

@override

void detach() {

super.detach();

Layer child = firstChild;

while (child != null) {

child.detach();

child = child.nextSibling;

}

}

//将child添加到链表中

void append(Layer child) {

adoptChild(child);

child._previousSibling = lastChild;

if (lastChild != null)

lastChild._nextSibling = child;

_lastChild = child;

_firstChild ??= child;

}

...

}

在上述的append函数中就将子节点添加到Layer树并加入到双链表中。在adoptChild函数中最终会调用attach函数,从而完成Layer树的添加。


##### 5.3. Layer的状态更新

class ContainerLayer extends Layer {

...

//更新Layer节点的状态。

@override

void updateSubtreeNeedsAddToScene() {

super.updateSubtreeNeedsAddToScene();

Layer child = firstChild;

while (child != null) {

child.updateSubtreeNeedsAddToScene();

_needsAddToScene = _needsAddToScene || child._needsAddToScene;

child = child.nextSibling;

}

}

...

}

updateSubtreeNeedsAddToScene函数就是更新Layer的状态,可以发现,在更新当前Layer的状态时,也会更新其所有子Layer的状态。


#### 6. compositing阶段:


将UI数据发送给GPU处理。


该阶段主要是将更新后的数据传递给GPU。这时候调用的是compositeFrame函数,该函数很简单,就是调用了一个Native函数。

void compositeFrame() {

Timeline.startSync('Compositing', arguments: timelineWhitelistArguments);

try {

final ui.SceneBuilder builder = ui.SceneBuilder();

final ui.Scene scene = layer.buildScene(builder);

if (automaticSystemUiAdjustment)

_updateSystemChrome();

//更新后数据交给GPU处理

_window.render(scene);

scene.dispose();

} finally {

Timeline.finishSync();

}

}

#### 7. semantics阶段:


与平台的辅助功能相关。


在向GPU发送数据后,Flutter还会调用flushSemantics函数。该函数与系统的辅助功能相关,一般情况下是不做任何处理。


#### 8. finalization阶段:


主要是从Element树中移除无用的Element对象及处理绘制结束回调。


在该阶段,主要是将Element对象从树中移除及处理添加在\_postFrameCallbacks集合中的回调函数。由于该回调函数是在绘制结束时调用,所以在该回调函数中,context已经创建成功。


#### 其他:


flushLayout触发布局,将RenderObject树的dirty节点通过调用performLayout方法进行逐一布局,我们先看一下RenderPadding中的实现

@override

void performLayout() {

_resolve();//解析padding参数

if (child == null) {//如果没有child,直接将constraints与padding综合计算得出自己的size

size = constraints.constrain(Size(

_resolvedPadding.left + _resolvedPadding.right,

_resolvedPadding.top + _resolvedPadding.bottom

));

return;

}

final BoxConstraints innerConstraints = constraints.deflate(_resolvedPadding);//将padding减去,生成新的约束innerConstraints

child.layout(innerConstraints, parentUsesSize: true);//用新的约束去布局child

final BoxParentData childParentData = child.parentData;

childParentData.offset = Offset(_resolvedPadding.left, _resolvedPadding.top);//设置childParentData的offset值

size = constraints.constrain(Size(//将constraints与padding以及child的sieze综合计算得出自己的size

_resolvedPadding.left + child.size.width + _resolvedPadding.right,

_resolvedPadding.top + child.size.height + _resolvedPadding.bottom

));

}

可以看到RenderPadding中的布局分两种情况。如果没有child,那么就直接拿parent传过来的约束以及padding来确定自己的大小;否则就先去布局child,让后再拿parent传过来的约束和padding以及child的size来确定自己的大小。RenderPadding是典型的单child的RenderBox,我们看一下多个child的RenderBox。例如RenderFlow

@override

void performLayout() {

size = _getSize(constraints);//直接先确定自己的size

int i = 0;

_randomAccessChildren.clear();

RenderBox child = firstChild;

while (child != null) {//遍历孩子

_randomAccessChildren.add(child);

final BoxConstraints innerConstraints = _delegate.getConstraintsForChild(i, constraints);//获取child的约束,此方法为抽象

child.layout(innerConstraints, parentUsesSize: true);//布局孩子

final FlowParentData childParentData = child.parentData;

childParentData.offset = Offset.zero;

child = childParentData.nextSibling;

i += 1;

}

}

可以看到RenderFlow的size直接就根据约束来确定了,并没去有先布局孩子,所以RenderFlow的size不依赖与孩子,后面依旧是对每一个child依次进行布局。


还有一种比较典型的树尖类型的RenderBox,LeafRenderObjectWidget子类创建的RenderObject对象都是,他们没有孩子,他们才是最终需要渲染的对象,例如

@override

void performLayout() {

size = _sizeForConstraints(constraints);

}

非常简单就通过约束确定自己的大小就结束了。所以performLayout过程就是两点,确定自己的大小以及布局孩子。我们上面提到的都是RenderBox的子类,这些RenderObject约束都是通过BoxConstraints来完成,但是RenderSliver的子类的约束是通过SliverConstraints来完成,虽然他们对child的约束方式不同,但他们在布局过程需要执行的操作都是一致的。


#### 绘制


布局完成了,PipelineOwner就通过flushPaint来进行绘制

void flushPaint() {

try {

final List dirtyNodes = _nodesNeedingPaint;

_nodesNeedingPaint = [];

// 对dirty nodes列表进行排序,最深的在第一位

for (RenderObject node in dirtyNodes...sort((RenderObject a, RenderObject b) => b.depth - a.depth)) {

assert(node._layer != null);

if (node._needsPaint && node.owner == this) {

if (node._layer.attached) {

PaintingContext.repaintCompositedChild(node);

} else {

node._skippedPaintingOnLayer();

}

}

}

} finally {}

}

PaintingContext.repaintCompositedChild(node)会调用到child.\_paintWithContext(childContext, Offset.zero)方法,进而调用到child的paint方法,我们来看一下第一次绘制的情况,dirty的node就应该是RenderView,跟进RenderView的paint方法

@override

void paint(PaintingContext context, Offset offset) {

if (child != null)

context.paintChild(child, offset);//直接绘制child

}

自己没有什么绘制的内容,直接绘制child,再看一下RenderShiftedBox

@override

void paint(PaintingContext context, Offset offset) {

if (child != null) {

final BoxParentData childParentData = child.parentData;

context.paintChild(child, childParentData.offset + offset);//直接绘制child

}

}

好像没有绘制内容就直接递归的进行绘制child,那找一个有绘制内容的吧,我们看看RenderDecoratedBox

@override

void paint(PaintingContext context, Offset offset) {

_painter ??= _decoration.createBoxPainter(markNeedsPaint);//获取painter画笔

final ImageConfiguration filledConfiguration = configuration.copyWith(size: size);

if (position == DecorationPosition.background) {//画背景

_painter.paint(context.canvas, offset, filledConfiguration);//绘制过程,具体细节再painter中

if (decoration.isComplex)

context.setIsComplexHint();

}

super.paint(context, offset);//画child,里面直接调用了paintChild

if (position == DecorationPosition.foreground) {//画前景

_painter.paint(context.canvas, offset, filledConfiguration);

if (decoration.isComplex)

context.setIsComplexHint();

}

}

如果自己有绘制内容,paint方法中的实现就应该包括绘制自己以及绘制child,如果没有孩子就只绘制自己的内容,看一下RenderImage

@override

void paint(PaintingContext context, Offset offset) {

if (_image == null)

return;

_resolve();

paintImage(//直接绘制Image,具体细节再此方法中

canvas: context.canvas,

rect: offset & size,

image: _image,

scale: _scale,

colorFilter: _colorFilter,

fit: _fit,

alignment: _resolvedAlignment,

centerSlice: _centerSlice,

repeat: _repeat,

flipHorizontally: _flipHorizontally,

相关推荐
又吹风_Bassy12 小时前
解决 Flutter Device Daemon 启动失败问题的实践记录
flutter·androidstudio·flutter daemon·file handles·daemon crash
Wuxiaoming1352 天前
Flutter Isolate解决耗时任务导致卡死
flutter
大厂在职_XpW2 天前
Flutter 完整开发实战详解(二、 快速开发实战篇)_0_10_flutter dio
windows·flutter
明似水2 天前
高效管理Dart和Flutter多包项目:Melos工具全解析
android·前端·flutter
大厂在职_fUk2 天前
Flutter完整开发实战详解(六、 深入Widget原理)
前端·javascript·flutter
天若子2 天前
flutter Selector 使用
flutter
早起的年轻人2 天前
Flutter List 的 every 如果回调函数抛出异常 应该如何处理
开发语言·python·flutter
健了个平_243 天前
【Cursor初体验】AI开发 Flutter 应用:一款快速预览SVGA的桌面小工具
前端·flutter·ios
1323 天前
独立开发项目阶段总结-需求只有自己有
android·前端·flutter