前言
如何快速高效的掌握一门知识,建议先阅读下这篇文章关于学习的一些看法。
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的逻辑和布局。
码字不易,记得关注 + 点赞 + 收藏