前言
如何快速高效的掌握一门知识,建议先阅读下这篇文章关于学习的一些看法。
Flutter渲染原理系列文章:
Flutter渲染原理系列之构建RenderObject树
Flutter渲染原理系列之合成Layer树~敬请期待
Flutter渲染原理系列之GPU渲染~敬请期待
Flutter渲染原理系列之屏幕刷新(Async)~敬请期待
Flutter渲染原理系列之runApp执行流程~敬请期待
一、RenderObject
概述
1、定义 ( An object in the render tree
):
RenderObject
是渲染树(Render Tree
)中的对象,是负责布局
和绘制
的类的基类
。
2、补充说明:
- 渲染树中的每个节点的基类都是
RenderObject
,该基类为布局和绘制定义了一个抽象模型。 - 每个
RenderObject
都了解其父节点的信息,但对子节点,除了如何访问和获取它们的布局约束,并没有更多的信息。这样设计让RenderObject
具有更高的抽象能力,能够处理各种各样的场景。
二、RenderObject
的基本类型
1、RenderView
:
- 渲染树的
根节点
。
2、RenderSliver
:
- 实现
滚动效果
的渲染对象的基类
。
3、RenderAbstractViewport
:
- 渲染
内部较大对象
的基类
,主要用于滚动
实现。
4、RenderBox
:
二维笛卡尔坐标系
中的渲染对象。大多数渲染组件的基类
。- 常用子类:
RenderFlex
、RenderGrid
、RenderPadding
、RenderStack
等。
三、RenderObject
的生命周期
RenderObject
的主要生命周期阶段:
1、创建阶段 (Attach
)
- 当一个
RenderObject
首次需要添加到渲染树中时,它会被创建,并通过attach
方法附加到树上。
2、布局阶段 (Layout
)
RenderObject
的布局方法layout
会在每次需要重新计算大小和位置时被调用。这包括但不限于:- 初始布局计算。
- 当父
RenderObject
的尺寸或约束变化时。 - 当
RenderObject
自身的属性(如颜色、大小、位置)发生变化时。
3、绘制阶段 (Paint
)
RenderObject
的paint
方法负责将其内容绘制到屏幕上。- 在每次帧的绘制阶段,
paint
方法会被调用。 - 绘制可能会依赖于布局阶段的结果,因为布局决定了
RenderObject
的最终位置和大小。
4、合成阶段
- 在
RenderObject
绘制完成后,它们可能需要与其他RenderObject
合成在一起形成最终的屏幕输出。
5、销毁 (分离) 阶段 (Detach
)
- 当
RenderObject
不再需要存在于渲染树中时,它会被从树上分离,并调用detach
方法。
总而言之,RenderObject
的生命周期主要围绕着它的创建
、布局
、绘制
、合成
和最终的销毁。RenderObject
是实际绘制和布局的实体。
四、RenderObject
的构建
dart
/// RenderObjectElement
@override
void mount(Element? parent, Object? newSlot) {
super.mount(parent, newSlot);
_renderObject = (widget as RenderObjectWidget).createRenderObject(this);
attachRenderObject(newSlot);
_dirty = false;
}
通过上图及代码所示:
Flutter
会为Element
树中的每个RenderObjectElement
创建对应的RenderObject
对象。
五、RenderObject
的布局
盒子限制模型
的简单说明:
- 布局的时间复杂度是 O(n)。
- 父节点可以通过设定最大和最小的尺寸限制,决定其子节点对象的大小。
- 父节点可以决定子节点的宽度,而让子节点灵活地自适应布局高度(或决定高度而自适应宽度)。
大部分的Widget
是由RenderBox
的子类的对象渲染的,它们呈现出的RenderObject
会在二维笛卡尔空间中拥有固定的大小。RenderBox
提供了盒子限制模型
,为每个Widget
关联了渲染的最小和最大的宽度和高度。
如上图所示,布局的时候会以DFS
(深度优先遍历
)方式遍历渲染树,并将限制以自上而下
的方式从父节点传递给子节点。子节点若要确定自己的大小,则必须
遵循父节点传递的限制。子节点的响应方式是在父节点建立的约束内将大小以自下而上
的方式传递给父节点。
RenderObject
布局简要说明:
1、约束传递:
- 布局开始于根
RenderObject
,通常这是RenderView
,它从系统接收屏幕尺寸作为约束。 - 约束沿着渲染树向下传递,每个
RenderObject
根据其父节点提供的约束计算其自身及其子节点的尺寸。
2、测量阶段:
- 当
RenderObject
接收到约束后,它会调用performLayout
方法。 - 对于
RenderBox
类,performLayout
会调用layout
方法。 layout
方法会考虑约束以及其自身的逻辑来决定如何布置其子RenderObject
。- 子
RenderObject
也会收到新的约束,这个过程会递归进行,直到渲染树的最底层。
3、布局阶段:
- 在
layout
方法中,RenderObject
会根据约束和测量阶段的结果来确定其子RenderObject
的大小和位置。 - 这个阶段涉及计算几何属性,例如偏移量、大小、边距、填充和边框等。
- 对于
RenderBox
,它使用paintBounds
来存储计算后的边界。
总而言之,layout
会触发performResize/performLayout
计算布局信息,同时也会递归
地调用子layout
去计算布局信息。递归
遍历完成后,将所有需要渲染的node
都将标记为markNeedsPaint
,等待之后的Paint
流程的处理。
六、RenderObject
的绘制
RenderObject
的绘制过程是在整个渲染流程的一个重要阶段,它发生在布局阶段之后。绘制过程负责将RenderObject
转换为实际的像素数据
,以便最终显示在屏幕上。下面是RenderObject
绘制的详细过程:
1、准备阶段:
- 在
RenderObject
的paint
方法被调用之前,会有一系列的准备工作。这包括设置适当的画布上下文,如当前的变换矩阵 、裁剪区域 、混合模式等。
2、绘制自身:
- 每个
RenderObject
都有一个paint
方法,该方法负责将RenderObject
自身的内容绘制到给定的画布上。 - 在
paint
方法中,RenderObject
会使用Canvas API
来绘制其视觉表现,这可能包括基础图形 、文字 、图像 、路径等。
3、绘制子节点:
- 如果
RenderObject
有子节点,它会在绘制自身之后,递归地调用子节点的paint
方法,让它们也绘制自身的内容。 - 子节点的绘制通常会基于当前
RenderObject
的坐标系和变换矩阵。
4、图层合成:
- 有些
RenderObject
可能需要创建自己的图层,比如为了硬件加速或者解决复杂的合成需求。这些图层在paint
过程中被创建,并且可能包含对子节点的引用。 - 图层会在绘制阶段之后被进一步处理,以优化渲染流程,减少重绘次数,并利用
GPU
加速。
5、复合和绘制图层:
- 在所有
RenderObject
都绘制完成后,如果存在图层,它们会被复合到一起。这个过程可能涉及到透明度、混合模式等效果的计算。 - 最终的图层树会被绘制到屏幕缓冲区,然后由
GPU
渲染到物理设备上。
6、脏检查与重绘:
Flutter
的渲染引擎会跟踪哪些RenderObject
需要重绘,这被称为"脏检查 "。只有当RenderObject
的状态改变时,才会触发重绘。- 重绘仅限于需要更新的最小范围,以节省计算资源。
7、呈现到屏幕:
- 一旦所有必要的
RenderObject
被绘制并且图层被合成,最终的帧就被提交到操作系统,然后由系统调度器显示在屏幕上。
总而言之,RenderObject
的绘制过程是高度依赖于其具体类型 的。不同的RenderObject
可能实现不同的paint
方法,以适应不同类型的内容,如文本、图像、形状等。此外,性能优化也是绘制过程中的一个重要方面,通过避免不必要的重绘和利用硬件加速来提升用户体验。
七、总结
在构建RenderObject
的过程中,Flutter
引擎会执行脏检查,只更新那些真正需要更新的部分,以提高效率。此外,RenderObject
的生命周期和状态管理是由Flutter
的渲染引擎自动处理的,开发者主要关注于Widget
的逻辑和布局。
码字不易,记得关注 + 点赞 + 收藏