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,

相关推荐
LawrenceLan10 小时前
Flutter 零基础入门(九):构造函数、命名构造函数与 this 关键字
开发语言·flutter·dart
一豆羹10 小时前
macOS 环境下 ADB 无线调试连接失败、Protocol Fault 及端口占用的深度排查
flutter
行者9610 小时前
OpenHarmony上Flutter粒子效果组件的深度适配与实践
flutter·交互·harmonyos·鸿蒙
行者9613 小时前
Flutter与OpenHarmony深度集成:数据导出组件的实战优化与性能提升
flutter·harmonyos·鸿蒙
小雨下雨的雨13 小时前
Flutter 框架跨平台鸿蒙开发 —— Row & Column 布局之轴线控制艺术
flutter·华为·交互·harmonyos·鸿蒙系统
小雨下雨的雨14 小时前
Flutter 框架跨平台鸿蒙开发 —— Center 控件之完美居中之道
flutter·ui·华为·harmonyos·鸿蒙
小雨下雨的雨14 小时前
Flutter 框架跨平台鸿蒙开发 —— Icon 控件之图标交互美学
flutter·华为·交互·harmonyos·鸿蒙系统
小雨下雨的雨14 小时前
Flutter 框架跨平台鸿蒙开发 —— Placeholder 控件之布局雏形美学
flutter·ui·华为·harmonyos·鸿蒙系统
行者9615 小时前
OpenHarmony Flutter弹出菜单组件深度实践:从基础到高级的完整指南
flutter·harmonyos·鸿蒙
前端不太难15 小时前
Flutter / RN / iOS,在长期维护下的性能差异本质
flutter·ios