| |
|
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
| -> 移动开发 -> 60、Flutter核心原理--绘制Compositing -> 正文阅读 |
|
|
[移动开发]60、Flutter核心原理--绘制Compositing |
|
本节我们来介绍一下 flushCompositingBits()。现在,我们再来回顾一下Flutter的渲染管线:
其中只有 flushCompositingBits() 还没有介绍过,这是因为要理解flushCompositingBits(),就必须的了解Layer是什么,以及 Layer 树构建的过程。为了更容易理解它,我们先看一个demo。 CustomRotatedBox我们实现一个CustomRotatedBox,它的功能是将其子元素放倒(顺时针旋转 90 度),要实现个效果我们可以直接使用 canvas 的变换功能,下面是核心代码:
下面我们写个demo测试一下:
运行效果如图14-17,A被成功放倒了:
|
| Layer的名称 | PaintingContext对应的方法 | Widget |
|---|---|---|
| ClipPathLayer | pushClipPath | ClipPath |
| OpacityLayer | pushOpacity | Opacity |
| ClipRRectLayer | pushClipRRect | ClipRRect |
| ClipRectLayer | pushClipRect | ClipRect |
| TransformLayer | pushTransform | RotatedBox、Transform |
通过上面的例子我们知道 CustomRotatedBox 的直接子节点是绘制边界节点时 CustomRotatedBox 中就需要合成 layer。实际上这只是一种特例,还有一些其它情况也需要 CustomRotatedBox 进行 Layer 合成,那什么时候需要 Layer 合成有没有一个一般性的普适原则?答案是:有! 我们思考一下 CustomRotatedBox 中需要 Layer 合成的根本原因是什么?如果 CustomRotatedBox 的所有后代节点都共享的是同一个PictureLayer,但是,一旦有后代节点创建了新的PictureLayer,则绘制就会脱离了之前PictureLayer,因为不同的PictureLayer上的绘制是相互隔离的,是不能相互影响,所以为了使变换对所有后代节点对应的 PictureLayer 都生效,则我们就需要将所有后代节点的添加到同一个 ContainerLayer 中,所以就需要在 CustomRotatedBox 中先进行 Layer 合成。
综上,一个普适的原则就呼之欲出了:当后代节点会向 layer 树中添加新的绘制类Layer时,则父级的变换类组件中就需要合成 Layer。下面我们验证一下:
现在我们修改一下上面的示例,给 RepaintBoundary 添加一个 Center 父组件:
@override
Widget build(BuildContext context) {
return Center(
child: CustomRotatedBox(
child: Center( // 新添加
child: RepaintBoundary(
child: Text(
"A",
textScaleFactor: 5,
),
),
),
),
);
}
因为 CustomRotatedBox 中只判断了其直接子节点的child!.isRepaintBoundary 为 true时,才会进行 layer 合成,而现在它的直接子节点是Center,所以该判断会是false,则不会进行层 layer 合成。但是根据我们上面得出的结论,RepaintBoundary 作为CustomRotatedBox 的后代节点且会向 layer 树中添加新 layer 时就需要进行 layer合成,该合成时没有合成,所以预期是不能将 "A" 放倒的,运行后发现效果和之前的图14-18相同:

?果然 ”A“ 并没有被放倒!看来我们的 CustomRotatedBox 还是需要继续修改。解决这个问题并不难,我们在判断是否需要进行 Layer 合成时,要去遍历整个子树,看看否存在绘制边界节点,如果是则合成,反之则否。为此,我们新定义一个在子树上查找是否存在绘制边界节点的 needCompositing() 方法:
//子树中递归查找是否存在绘制边界
needCompositing() {
bool result = false;
_visit(RenderObject child) {
if (child.isRepaintBoundary) {
result = true;
return ;
} else {
//递归查找
child.visitChildren(_visit);
}
}
//遍历子节点
visitChildren(_visit);
return result;
}
然后需要修改一下 paint 实现:?
@override
void paint(PaintingContext context, Offset offset) {
if (child != null) {
context.pushTransform(
needCompositing(), //子树是否存在绘制边界节点
offset,
_paintTransform!,
_paintChild,
oldLayer: _transformLayer.layer,
);
} else {
_transformLayer.layer = null;
}
}
现在,我们再来运行一下demo,运行后效果和图14-17相同:

又成功放倒了!但还有问题,我们继续往下看。
我们考虑一下这种情况:如果 CustomRotatedBox 的后代节点中没有绘制边界节点,但是有后代节点向 layer 树中添加了新的 layer。这种情况下,按照我们之前得出的结论 CustomRotatedBox 中也是需要进行 layer 合成的,但 CustomRotatedBox 实际上并没有。问题知道了,但是这个问题却不好解决,原因是我们在 CustomRotatedBox 中遍历后代节点时,是无法知道非绘制边界节点是否往 layer树中添加了新的 layer。怎么办呢?Flutter是通过约定来解决这个问题的:
RenderObject 中定义了一个布尔类型 alwaysNeedsCompositing 属性。
约定:自定义组件中,如果在组件 isRepaintBoundary 为 false 时,在绘制时会向 layer 树中添加新的 layer的话,要将 alwaysNeedsCompositing 置为 true 。
只要开发者在自定义组件时遵守这个规范,CustomRotatedBox 中我们在子树中递归查找时的判断条件就可以改为:
child.isRepaintBoundary || child.alwaysNeedsCompositing
needCompositing 实现如下:?
//子树中递归查找是否存在绘制边界
needCompositing() {
bool result = false;
_visit(RenderObject child) {
// 修改判断条件改为
if (child.isRepaintBoundary || child.alwaysNeedsCompositing) {
result = true;
return ;
} else {
child.visitChildren(_visit);
}
}
visitChildren(_visit);
return result;
}
?
下面我们看一下 flutter 中 Opacity 组件的实现。
Opacity 可以对子树进行透明度控制,这个效果通过 canvas 是很难实现的,所以 flutter 中直接使用了 OffsetLayer 合成的方式来实现:
class RenderOpacity extends RenderProxyBox {
// 本组件是非绘制边界节点,但会在部分透明的情况下向layer树中添加新的Layer,所以部分透明时要返回 true
@override
bool get alwaysNeedsCompositing => child != null && (_alpha != 0 && _alpha != 255);
@override
void paint(PaintingContext context, Offset offset) {
if (child != null) {
if (_alpha == 0) {
// 完全透明,则没必要再绘制子节点了
layer = null;
return;
}
if (_alpha == 255) {
// 完全不透明,则不需要变换处理,直接绘制子节点即可
layer = null;
context.paintChild(child!, offset);
return;
}
// 部分透明,需要通过OffsetLayer来处理,会向layer树中添加新 layer
layer = context.pushOpacity(offset, _alpha, super.paint, oldLayer: layer as OpacityLayer?);
}
}
}
注意,上面我们通过 CustomRotatedBox 演示了变换类组件的核心原理,不过还有一些优化的地方,比如:
Flutter 也考虑到了这个问题,于是便有了flushCompositingBits 方法,我们下面来正式介绍它。
每一个节点(RenderObject中)都有一个_needsCompositing 字段,该字段用于缓存当前节点在绘制子节点时是否需要合成 layer。flushCompositingBits 的功能就是在节点树初始化和子树中合成信息发生变化时来重新遍历节点树,更新每一个节点的_needsCompositing 值。可以发现:
完美的解决了我们之前提出的问题,下面我们看一下具体实现:
void flushCompositingBits() {
// 对需要更新合成信息的节点按照节点在节点树中的深度排序
_nodesNeedingCompositingBitsUpdate.sort((a,b) => a.depth - b.depth);
for (final RenderObject node in _nodesNeedingCompositingBitsUpdate) {
if (node._needsCompositingBitsUpdate && node.owner == this)
node._updateCompositingBits(); //更新合成信息
}
_nodesNeedingCompositingBitsUpdate.clear();
}
void _updateCompositingBits() {
if (!_needsCompositingBitsUpdate)
return;
final bool oldNeedsCompositing = _needsCompositing;
_needsCompositing = false;
// 递归遍历查找子树, 如果有孩子节点 needsCompositing 为true,则更新 _needsCompositing 值
visitChildren((RenderObject child) {
child._updateCompositingBits(); //递归执行
if (child.needsCompositing)
_needsCompositing = true;
});
// 这行我们上面讲过
if (isRepaintBoundary || alwaysNeedsCompositing)
_needsCompositing = true;
if (oldNeedsCompositing != _needsCompositing)
markNeedsPaint();
_needsCompositingBitsUpdate = false;
}
?执行完毕后,每一个节点的_needsCompositing 就确定了,我们在绘制时只需要判断一下当前的 needsCompositing(一个getter,会直接返回_needsCompositing ) 就能知道子树是否存在剥离layer了。这样的话,我们可以再优化一下 CustomRenderRotatedBox 的实现,最终的实现如下:
class CustomRenderRotatedBox extends RenderBox
with RenderObjectWithChildMixin<RenderBox> {
Matrix4? _paintTransform;
@override
void performLayout() {
_paintTransform = null;
if (child != null) {
child!.layout(constraints, parentUsesSize: true);
size = child!.size;
//根据子组件大小计算出旋转矩阵
_paintTransform = Matrix4.identity()
..translate(size.width / 2.0, size.height / 2.0)
..rotateZ(math.pi / 2)
..translate(-child!.size.width / 2.0, -child!.size.height / 2.0);
} else {
size = constraints.smallest;
}
}
final LayerHandle<TransformLayer> _transformLayer =
LayerHandle<TransformLayer>();
void _paintChild(PaintingContext context, Offset offset) {
print("paint child");
context.paintChild(child!, offset);
}
@override
void paint(PaintingContext context, Offset offset) {
if (child != null) {
_transformLayer.layer = context.pushTransform(
needsCompositing, // pipelineOwner.flushCompositingBits(); 执行后这个值就能确定
offset,
_paintTransform!,
_paintChild,
oldLayer: _transformLayer.layer,
);
} else {
_transformLayer.layer = null;
}
}
@override
void dispose() {
_transformLayer.layer = null;
super.dispose();
}
@override
void applyPaintTransform(RenderBox child, Matrix4 transform) {
if (_paintTransform != null) transform.multiply(_paintTransform!);
super.applyPaintTransform(child, transform);
}
}
现在,我们思考一下引入 flushCompositingBits 的根本原因是什么?假如我们在变换类容器中始终采用合成 layer 的方式来对子树应用变换效果,也就是说不再使用 canvas 进行变换,这样的话 flushCompositingBits 也就没必要存在了,为什么一定要 flushCompositingBits 呢?根本原因就是:如果在变换类组件中一刀切的使用合成 layer 方式的话,每遇到一个变换类组件则至少会再创建一个 layer,这样的话,最终 layer 树上的layer数量就会变多。我们之前说过对子树应用的变换效果既能通过 Canvas 实现也能通过容器类Layer实现时,建议使用Canvas 。这是因为每新建一个 layer 都会有额外的开销,所以我们只应该在无法通过 Canvas 来实现子树变化效果时再通过Layer 合成的方式来实现。综上,我们可以发现引入 flushCompositingBits 的根本原因其实是为了减少 layer的数量。
另外,flushCompositingBits 的执行过程只是做标记,并没有进行层的合成,真正的合成是在绘制时(组件的 paint 方法中)。
首先只有组件树中有变换类容器时,才有可能需要重新合成 layer;如果没有变换类组件,则不需要。
当变换类容器的后代节点会向 layer 树中添加新的绘制类 layer 时,则变换类组件中就需要合成 layer。
引入 flushCompositingBits 的根本原因是为了减少 layer 的数量。
?
|
|
| 移动开发 最新文章 |
| Vue3装载axios和element-ui |
| android adb cmd |
| 【xcode】Xcode常用快捷键与技巧 |
| Android开发中的线程池使用 |
| Java 和 Android 的 Base64 |
| Android 测试文字编码格式 |
| 微信小程序支付 |
| 安卓权限记录 |
| 知乎之自动养号 |
| 【Android Jetpack】DataStore |
|
|
| 上一篇文章 下一篇文章 查看所有文章 |
|
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
| 360图书馆 购物 三丰科技 阅读网 日历 万年历 2025年11日历 | -2025/11/29 18:26:34- |
|
| 网站联系: qq:121756557 email:121756557@qq.com IT数码 |