Flutter 原理:三棵重要的树 (渲染过程、布局约束,android 开发框架介绍
子控件也会创建相应 Element 被放在元素树上:
4Element 中的状态
我们上文提到了 Widget 的不可变性,相应的 Element 就有其可变性,正如我们前文所说的它被标记为 dirty Element 便是作为需要更新的状态,另外一个我们需要格外注意的是,有状态组件(StatefulWidget)对应的 State 对象其实也被 Element 所管理,如下图所示。
Flutter 中的 Widget 一直在重建,每次重建之后,Element 都会采用相应的措施来确定是否我对应的新控件跟之前引用旧控件是否有所改变,如果没改变则只需要做更新操作,如果前后不同则会重创建。那么,Element 根据什么来确定控件是否改变呢?它会比较 Widget 以下两个属性:
组件类型
Widget 的 Key (如果有)
组件类型即前后控件的是否是同一个类所创建的,Key 即为每个控件的唯一标识。
5 渲染树详解
我们已经大致知道 Flutter 中的三棵重要的树及 Element 树的工作原理,其中第三棵渲染树的任务就是做组件的具体的布局渲染工作。
渲染树上每个节点都是一个继承自 RenderObject 类的对象,其由 Element 中的 renderObject 或? RenderObjectWidget 中的 createRenderObject 方法生成,该对象内部提供多个属性及方法来帮助框架层中的组件如何布局渲染。
"
我们知道 StatelessWidget 和 StatefulWidget 两种直接继承自 Widget 的类,在 Flutter 中,还有另一个类 RenderObjectWidget 也同样直接继承自 Widget,它没有 build 方法,可通过 createRenderObject 直接创建 RenderObject 对象放入渲染树中。Column 和 Row 等控件都间接继承自 RenderObjectWidget
"
主要属性和方法如下:
constraints 对象,从其父级传递给它的约束
parentData 对象,其父对象附加有用的信息。
performLayout 方法,计算此渲染对象的布局。
paint 方法,绘制该组件及其子组件。
RenderObject 作为一个抽象类。每个节点需要实现它才能进行实际渲染。扩展 RenderOject 的两个最重要的类是 RenderBox 和 RenderSliver。这两个类分别是应用了 Box 协议和 Sliver 协议这两种布局协议的所有渲染对象的父类,其还扩展了数十个和其他几个处理特定场景的类,并实现了渲染过程的细节,如 RenderShiftedBox 和 RenderStack 等等。
布局约束
在上面,我们介绍组件渲染流程时,我们了
解到了 Flutter 中的控件在屏幕上绘制渲染之前需要先进行布局(Layout)操作。其具体可分为两个线性过程:从顶部向下传递约束,从底部向上传递布局信息,其过程可用下图表示。
第一个线性过程用于传递布局约束。父节点给每个子节点传递约束,这些约束是每个子节点在布局阶段必须要遵守的规则。就好像父母告诉自己的孩子 :“你必须遵守学校的规定,才可以做其他的事”。常见的约束包括规定子节点最大最小宽度或者子节点最大最小的高度。这种约束会向下延伸,子组件也会产生约束传递给自己的孩子,一直到叶子结点。
第二的线性过程用来传递具体的布局信息。子节点接受到来自父节点的约束后,会依据它产生自己具体的布局信息,如父节点规定我的最小宽度是 500 的单位像素,子节点按照这个规则可能定义自己的宽度为 500 个像素,或者大于 500 像素的任何一个值。这样,确定好自己的布局信息之后,将这些信息告诉父节点。父节点也会继续此操作向上传递一直到最顶部。
下面我们具体介绍有哪些具体的布局约束可在树中传递。Flutter 中有两种主要的布局协议:Box 盒子协议和 Sliver 滑动协议。这里我们以盒子协议为例展开具体的介绍。
在盒子协议中,父节点传递给其子节点的约束为 BoxConstraints。该约束规定了允许每个子节点的最大和最小宽度和高度。如下图,父节点传入 Min Width 为 150,Max Width 为 300 的 BoxConstraints:
当子节点接受到该约束,便可以取得上图中绿色范围内的值,即宽度在 150 到 300 之间,高度大于 100,当取得具体的值之后再将取得具体的大小的值上传给父节点,从而达到父子的布局通信。
6 自定义一个 Center 控件
现在,我们可以应用前文中提到的布局约束与渲染树相关的概念自己定义一个类似居中布局的组件 RenderObject 对象渲染在屏幕上。
所以我们称自己自定义组件为 CustomCenter
void main() {
现在我们来实现我们的 CustomCenter:
class CustomCenter extends SingleChildRenderObjectWidget {
CustomCenter?继承了
SingleChildRenderObjectWidget,表明这个 Widget 只能有一个子控件,其中,createRenderObject(...) 方法用于真正创建并返回我们的 RenderObject 对象实例, 我们的 RenderObject 为 RenderCustomCenter,代码如下:
class RenderCustomCenter extends RenderShiftedBox {
RenderCustomCenter 继承自?
RenderShiftedBox,该类是继承自 RenderBox。RenderShiftedBox 满足盒子协议,并且提供了 performLayout() 方法的实现。我们需要在 performLayout() 方法中布局我们的子元素。
我们在使用 child.layout(...) 方法布局 child 的时候传递了两个参数,第一个为 child 的布局约束,而另外一个参数是 parentUserSize, 该参数如果设置为 false,则意味着 parent 不关心 child 选择的大小,这对布局优化比较有用;因为如果 child 改变了自己的大小,parent 就不必重新 layout 了。但是在我们的例子中,我们的需要把 child 放置在 parent 的中心,就是 child 的大小(Size)一旦改变,则其对应的偏移量(Offset) 也会改变,于是 parent 需要重新布局,所以我们这里传递了一个 true。
当 child.layout(...) 完成了以后,child 就确定了自己的 Layout Details。然后我们就还可以为其设置偏移量来将它放置到我们想放的位置。在我们的例子中为 居中。
最后,和 child 根据 parent 传递过来的约束选择了一个尺寸一样,我们也需要为 CustomCenter 选择一个尺寸。
运行效果如下:
7 应用视图的构建
Flutter App 入口的部分发生于如下代码:
import 'package:flutter/material.dart';
runApp 函数接受一个 Widget 类型的对象作为参数,也就是说在 Flutter 的概念中,只存在 View,而其他的任何逻辑都只为 View 的数据、状态改变服务,不存在 ViewController(或者叫 Activity)。接下来看 runApp 做了什么:
评论