上一节介绍了dart的安装和使用,这一节让我们一起正式进入flutter的世界里。好的,话不多说,让我们一起开启flutter的探索之旅吧!
第一节,我们先一起来熟悉一下flutter中遍地的widget,了解一下它的结构和特点。
我们在开始学习flutter的时候,最先接触到的widget是StatefulWidget和StatelessWidget这两个基本组件,既然如此,那我们先来单独解析一下这两个组件的基本结构。
abstract class StatefulWidget extends Widget {
const StatefulWidget({ Key key }) : super(key: key);
@override
StatefulElement createElement() => StatefulElement(this);
@protected
State createState();
}
abstract class StatelessWidget extends Widget {
const StatelessWidget({ Key key }) : super(key: key);
@override
StatelessElement createElement() => StatelessElement(this);
@protected
Widget build(BuildContext context);
}
我们从上面它们的实现中可以了解到,代码量仅仅数行,其都继承自Widget组件类,并且都实现了来自基类Widget中的createElement()函数,创建出一个element对象。下面是基类Widget的代码结构:
abstract class Widget extends DiagnosticableTree {
const Widget({ this.key });
final Key key;
@protected
Element createElement();
static bool canUpdate(Widget oldWidget, Widget newWidget) {
return oldWidget.runtimeType == newWidget.runtimeType
&& oldWidget.key == newWidget.key;
}
}
基类Widget的代码量也是极其少的,它继承自DiagnosticableTree类,而DiagnosticableTree根据它名称含义,就是可诊断的树,无非就是产生一些有关widget配置数据的诊断信息(这个就不具体介绍了)。我们重点关注成员属性key和抽象函数createElement()还有一个静态函数canUpdate()。这也表明了只要实现了Widget类的组件都具备可配置key和创建element元素的特性,而静态函数canUpdate()主要用来比较两个widget组件的类型和配置的key是否都一致。
好,我们言归正传,继承了Widget组件的StatelessWidget和StatefulWidget的不同点是什么呢?就他们命名的含义来说:StatelessWidget意为无状态的组件;而StatefulWidget意为有状态的组件。无状态的组件StatelessWidget抽象出一个build()函数,该build函数需要传入一个BuildContext对象,并且返回一个Widget类型的组件。接触过flutter的童鞋们都知道build()是写flutter程序必需的函数,可以构建出我们想要的用户界面。那么,StatefulWidget的build()函数又在哪里?我们从StatefulWidget的代码结构可以了解到,虽然StatefulWidget没有build()函数,但是他有一个StatelessWidget不具备的函数createState(),该函数返回一个State对象,这就是StatelessWidget叫做有状态对象的实现之一,我们具体来看一下State类的代码实现:
//继承State对象的子类可以指明泛型T的具体类型,该T类型必须是StatefuleWidget组件类的子类型。
//通过对泛型T和内部成员属性的理解,可以得知以下两点信息:
//1.任何继承State的子类都拥有一个具备StatefuleWidget特性的组件。
//2.任何继承State的子类都拥有一个具备StatefuleElement特性的元素。
abstract class State extends Diagnosticable {
//可以保存一个widget组件对象
T get widget => _widget;
T _widget;
//可以保存一个BuildContext类型的上下文,
//然而这里的上下文实质上就是一个StatefulElement元素对象。
//这说明了任何StatefulElement都具有BuildContext特性。
BuildContext get context => _element;
StatefulElement _element;
//mounted的值跟元素是否为空相关
bool get mounted => _element != null;
//初始化状态
//@protected注释表明该函数可以被子类重写
//@mustCallSuper注释似乎在告诉子类,如果你想重写我,
//必须使用super.initState()调用到我的功能。
@protected
@mustCallSuper
void initState() {}
//当组件已经更新的时候会触发该函数的调用,
//这里可以拿着新组件widget和旧组件oldWidget
//处理一些你认为需要的逻辑(一般可以用来做数据优化)
@mustCallSuper
@protected
void didUpdateWidget(covariant T oldWidget) { }
//意为重新装配,
//可以实现你认为比较重要的处理过程,
//该函数的触发点在后面小节中可能会介绍
@protected
@mustCallSuper
void reassemble() { }
//设置状态
//1.首先会调用到传进来的函数fn()
//2.继而会调用到元素的markNeedsBuild()函数,该函数在后面章节中分析说明
@protected
void setState(VoidCallback fn) {
final dynamic result = fn() as dynamic;
_element.markNeedsBuild();
}
//停用功能(比如移除元素的时候)
//为什么不直接销毁?增加此功能的目的可能是
//为了保留被移除的元素,也就是说元素可能都
//具有被复用的特性。
@protected
@mustCallSuper
void deactivate() { }
//丢弃功能
//该功能一旦调用,元素对象也就不能被再次复用了
@protected
@mustCallSuper
void dispose() { }
//当依赖发生改变的时候会调用到此函数,
//什么依赖会发生什么样的改变?猜测可能跟元素节点之间的依赖关系相关,
//在稍后的章节中一起来分析下。
@protected
@mustCallSuper
void didChangeDependencies() { }
//状态类中具备构建能力的函数。
//此函数为抽象函数,子类必须实现。
@protected
Widget build(BuildContext context);
}
从上面的代码中我们知道任何状态对象中都可以关联一个组件对象StatefulWidget和一个元素对象StatefulElement,而创建状态的责任由StatefulWidget对象负责,虽然StatefulWidget中没有和StatelessWidget类似的构建函数build(),但是在它的状态对象中存在,这样就相当于StatefulWidget也具备了和StatelessWidget相同的构建能力了。我们知道StatefuleWidget的状态和构建工作都保存在一个叫做State的对象中,而开发者只需要创建出一个State的子类即可轻松实现有状态的组件了,我们一般将状态数据和构造工作都放在同一个State状态对象内部,这很大的方便了我们在构建过程中对状态的管理。那么让状态对象State关联上StatefulWidget和StatefulElement这两个重要的类由谁负责呢?这个我们在介绍element一节中再继续讨论。
那么除了上面介绍的两种类型的widget组件,就没有其他widget组件了吗?答案是当然不止这两种。我们从上面的介绍中就可以了解到,StatelessWidget和StatefulWidget这两种组件的特性主要就两种:
1. 创建元素element。
2. 利用build()函数构建其他类型的Widget组件。
What?其他类型的Widget组件?是的,你没听错,还有其他不同于build特性的Widget组件。当我们想要创建自己定义的Widget组件的时候,实现的也就是这些其他类型的Widget组件了。
第二节我们来了解一下flutter中的其他类型的组件之RenderObjectWidget组件(这里只介绍重要的组件)。前一小节我们简单介绍了一下flutter两种基本构建组件:无状态的组件StatelessWidget和有状态的组件StatefuleWidget,他们的主要作用是build()出我们可以在界面上显示的组件,当然build()中也可以存在无状态的组件StatelessWidget和有状态的组件StatefuleWidget,但是build()内部大部分都是其他类型的组件RenderObjectWidget,而RenderObjectWidget从字面上理解为渲染对象的组件,因为我们构建的目的最终只有一个,就是能在手机界面上显示出这些组件所描述的样子。那么RenderObjectWidget的作用就显而易见了,主要就是用来显示组件的,然而想要显示出界面,必然需要布局和绘制这两个基本步骤。我们一起来看看ReaderObjectWidget类的代码结构:
abstract class RenderObjectWidget extends Widget {
const RenderObjectWidget({ Key key }) : super(key: key);
@override
RenderObjectElement createElement();
@protected
RenderObject createRenderObject(BuildContext context);
@protected
void updateRenderObject(BuildContext context, covariant RenderObject renderObject) { }
@protected
void didUnmountRenderObject(covariant RenderObject renderObject) { }
}
我们从组件RenderObjectWidget类中的代码结构可以了解到,RenderObjectWidget组件和StatefuleWidget、StatelessWidget的共同点在于,都具备创建元素的能力createElement(),并且都拥有属性key。不同点在于RenderObjectWidget组件具备创建RenderObject对象的能力createRenderObject(),并且需要传入一个BuildContext类型的参数。其余两个函数从字面意义上理解无非就是还拥有更新和卸载RenderObject对象的能力。但是RenderObjectWidget类显示组件的能力怎么没看出来?这里就要我们深入到RenderObject类里面一探究竟,下面是RenderObject类的继承关系:
从上图可以看出RenderObject主要继承了AbstractNode和HitTestTarget两个类,而AbstractNode是抽象节点的含义,也就是说RenderObject具备抽象节点的相关特性;而HitTestTarget顾名思义为敲击测试目标,这就跟我们的点击事件相关了,也就是说任何RenderObject对象都具备可点击的特性。下面是AbstractNode类的代码结构:
//该类仅仅存在父节点内容,
//意味着子节点的实现逻辑交由其子类负责。
//covariant关键词代表子类可以缩小形参的类型。
class AbstractNode {
//意味着每个节点都具有深度值
int get depth => _depth;
int _depth = 0;
//提供一个计算子节点的函数,此处表明
//子节点的深度值永远大于父节点的深度值。
//但是暂不清楚子节点的来源。
@protected
void redepthChild(AbstractNode child) {
if (child._depth <= _depth) {
child._depth = _depth + 1;
child.redepthChildren();
}
}
//计算所有子节点的深度。
//该函数为空实现,想必是交由其子类具体实现,
//猜测:子类实现应该会利用redepthChild()进行深度计算。
void redepthChildren() { }
//owner对象的实际意义暂不清楚,应该是由子类确定
Object get owner => _owner;
Object _owner;
bool get attached => _owner != null;
//节点被添加到节点树中的时候被调用
//一般在父节点关联子节点的时候调用
@mustCallSuper
void attach(covariant Object owner) {
_owner = owner;
}
//节点从节点树中移除的时候会被调用
//一般在父节点移除子节点的时候调用
@mustCallSuper
void detach() {
_owner = null;
}
//声明了一个父节点属性
AbstractNode get parent => _parent;
AbstractNode _parent;
//在关联子节点时,该函数会触发子节点的attach()函数
//1.确定子节点的父引用
//2.将owner传递给子节点
//3.计算子节点的深度
@protected
@mustCallSuper
void adoptChild(covariant AbstractNode child) {
child._parent = this;
if (attached)
child.attach(_owner);
redepthChild(child);
}
//在移除子节点时,该函数会触发子节点的detach()函数
//1.删除子节点的父引用
//2.删除子节点的owner引用
@protected
@mustCallSuper
void dropChild(covariant AbstractNode child) {
child._parent = null;
if (attached)
child.detach();
}
}
从上面的代码中可以看出,AbstractNode对象可以通过属性"parent"形成一棵链式集合的节点树,并且可以按照深度进行排列,而且一棵节点树集合只管理同一个owner对象,也就是说同一棵节点树只会产生唯一的一个owner实例,并且每个节点对象对owner的引用都会在attach()被调用时指定,并且在detch()被调用时删除,由此可证明owner对象是整棵节点树的纽带所在。而RenderObject就具备了AbstractNode的所有特性。同时RenderObject也具备可点击的特性,HitTestTarget类代码结构如下所示:
abstract class HitTestTarget {
factory HitTestTarget._() => null;
void handleEvent(PointerEvent event, HitTestEntry entry);
}
抽象类HitTestTarget只存在一个可用函数handleEvent(),该函数需要传递两个不同类型的对象:PointerEvent和HitTestEntry,这里就简单的阐述一下这两个对象的作用,不详细进行代码分析了:
1. PointEvent为指针事件,其实就是点击事件的一个模型对象,里面存储了点击相关的数据,并且提供了跟矩阵转换Matrix4相关的函数,但是矩阵转换不影响事件本身。
2. HitTestEntry内部可以存储一个HitTestTarget和一个Matrix4对象。
总结,RenderObject具备了AbstractNode和HitTestTarget的特性,因此,任何实现了RenderObject的子类都可以组成一棵节点树并且都可以进行点击事件的传递。下面我们来具体分析一下其代码结构(这里我们单独提取其重要的函数来讲解):
class ParentData {
//当RenderObject从节点树中移除时调用
@protected
@mustCallSuper
void detach() { }
}
abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin implements HitTestTarget {
......
//重写了AbstractNode的owner()函数,使owner对象具备实际意义
PipelineOwner get owner => super.owner;
//每一个RenderObject对象都可以保存一个ParentData的对象
//该类型就名称而言意为父节点的数据,该类只存在一个detach()函数,
//而ParentData对象的detach函数是空实现,因此不同RenderObject的子类
//可能会实现不同ParentData的子类逻辑。
ParentData parentData;
//默认值为true,说明markNeedsLayout()函数执行无效
bool _needsLayout = true;
//根据markNeedsPaint()函数中的逻辑,我们知道:
//1.当_relayoutBoundary不等于本节点,代表需要重新布局父节点
//2.当_relayoutBoundary等于本节点,代表只需要重新布局本节点
RenderObject _relayoutBoundary;
//访问子节点,空实现,具体交给子类实现
//每一种RenderObject的子类实现方式不一样,
//visitChildren(child)则由子类根据不同的情况具体实现
void visitChildren(RenderObjectVisitor visitor) { }
//调用该函数会为子节点的父数据对象赋值,该函数在不同的RenderObject子类中应该
//有不同的实现,默认创建的是具有空实现函数的ParentData对象。
void setupParentData(covariant RenderObject child) {
if (child.parentData is! ParentData)
child.parentData = ParentData();
}
//在关联子节点时会调用,新的子节点会传入(该函数会触发子节点的attach()被调用)
@override
void adoptChild(RenderObject child) {
//为子节点父数据对象赋值
setupParentData(child);
//触发一次布局流程
markNeedsLayout();
markNeedsCompositingBitsUpdate();
markNeedsSemanticsUpdate();
//继续调用父类AbstractNode的adoptChild()函数(会让子节点和本节点建立父子关系)
super.adoptChild(child);
}
//在移除子节点时会调用,旧的子节点会传入(该函数会触发子节点的detach()被调用)
@override
void dropChild(RenderObject child) {
//清除子节点的_relayoutBoundary和_needsLayout
child._cleanRelayoutBoundary();
//此处会触发子节点parentData对象中唯一的函数detach()
child.parentData.detach();
child.parentData = null;
//继续调用父类AbstractNode的dropChild()函数(会斩断子节点和它父节点的关系)
super.dropChild(child);
//因为销毁了子节点,本节点的布局可能会发生改变,重
//新触发一次布局的过程
markNeedsLayout();
markNeedsCompositingBitsUpdate();
markNeedsSemanticsUpdate();
}
//本节点attach函数在被父节点关联时会通过父节点调用到
@override
void attach(PipelineOwner owner) {
super.attach(owner);
if (_needsLayout && _relayoutBoundary != null) {
//在关联时如果_relayoutBoundary不为空,就触发一次布局流程
_needsLayout = false;
markNeedsLayout();
}
...
if (_needsPaint && _layer != null) {
//在关联时如果_layer不为空,就触发一次绘制流程
_needsPaint = false;
markNeedsPaint();
}
...
}
//本节点在节点树中被移除时会调用:
//1.当_relayoutBoundary为父节点,则清除_relayoutBoundary对父节点的引用,并且
//将_needsLayout恢复默认值true,表明已被移除树外的节点不具有调用markNeedsLayout()的功能;
//2.当_relayoutBoundary为本节点时,则不会清除_relayoutBoundary,当本节点重新被关联到其他
//父节点时会进行一次布局流程
void _cleanRelayoutBoundary() {
if (_relayoutBoundary != this) {
_relayoutBoundary = null;
_needsLayout = true;
visitChildren((RenderObject child) {
child._cleanRelayoutBoundary();
});
}
}
//主要目的是将需要布局的节点添加到owner._nodesNeedingLayout集合中
//继承AbstractNode的RenderObject对象具有链式集合的特性,因此只需要找到
//最顶层需要布局的节点(也就是最大父节点)添加到集合中即可。此函数的作用:
//找到最大需要布局的父节点,并触发owner.requestVisualUpdate()函数
void markNeedsLayout() {
//已经调用了该函数就直接返回,意味着
//markNeedsLayout()函数只会存在一次有效的调用
if (_needsLayout) {
return;
}
//当_relayoutBoundary不等于本节点就继续检查父节点,一般_relayoutBoundary=this
//时,可将布局的最大范围固定为自身,相应的布局的次数也会减少
if (_relayoutBoundary != this) {
markParentNeedsLayout();
} else {
//标记本次markNeedsLayout()函数已经进行了有效的调用
_needsLayout = true;
if (owner != null) {
//添加到需要布局的集合中
owner._nodesNeedingLayout.add(this);
//调用PipelineOwner的requestVisualUpdate()
//此函数的作用稍后再谈
owner.requestVisualUpdate();
}
}
}
//检查并布局父节点
void markParentNeedsLayout() {
_needsLayout = true;
final RenderObject parent = this.parent;
if (!_doingThisLayoutWithCallback) {
parent.markNeedsLayout();
}
}
//主要目的是将需要绘制的节点添加到owner._nodesNeedingPaint集合中,
//如果isRepaintBoundary为true则表明本节点需要进行绘制,否则继续检
//查父节点是否需要进行绘制,意味如果改变isRepaintBoundary的值可以
//控制绘制的范围,通过isRepaintBoundary可以优化节点被绘制的次数。
//想要真正触发绘制同布局一样也需要触发owner.requestVisualUpdate()函数
void markNeedsPaint() {
//已经调用了该函数就直接返回,意味着多次调用
//markNeedsPaint()函数只会存在一次有效的调用
if (_needsPaint)
return;
//标记本次markNeedsPaint()函数已经进行了有效的调用
_needsPaint = true;
if (isRepaintBoundary) {
if (owner != null) {
owner._nodesNeedingPaint.add(this);
owner.requestVisualUpdate();
}
} else if (parent is RenderObject) {
final RenderObject parent = this.parent;
parent.markNeedsPaint();
} else {
if (owner != null)
owner.requestVisualUpdate();
}
}
//实际的布局(不计算大小)
void _layoutWithoutResize() {
//该函数为一个抽象函数,具体的布局逻辑交由子类具体实现,
//意味着不同的RenderObject子类布局的方式有所不同
performLayout();
//暂时不去讨论该函数
markNeedsSemanticsUpdate();
//_layoutWithoutResize一但被调用就需要进行一次有效的布局
//表明markNeedsLayout()函数会起作用
_needsLayout = false;
//触发绘制相关的函数调用(每次布局时会检查是否需要进行绘制)
markNeedsPaint();
}
//实际的绘制
void _paintWithContext(PaintingContext context, Offset offset) {
//如果已经做了有效的布局,就可以进行实际的绘制了,否则就直接返回。
//意味着只有markNeedsPaint()函数先被有效的调用了才能调用本函数
if (_needsLayout)
return;
//标记markNeedsPaint()函数又可以进行一次有效的调用了
_needsPaint = false;
//类似于performLayout()函数,该函数也是一个抽象函数,
//意味着不同的RenderObject子类具体的绘制方式有所不同
paint(context, offset);
}
......
}
通过上面的代码分析,我们对RenderObject类又有了进一步的认识。RenderObject(渲染对象)两个最重要的过程无非是布局和绘制,那么他们的调用时机也是隐藏在上图中的代码里,周所周知,组件的显示遵循先布局后绘制的规则。上图的代码注释中有两个非常重要的函数:实际的布局函数_layoutWithoutResize()和实际的绘制函数_paintWithContext()。这两个函数什么时候会被调用从上面简单的代码结构中暂时看不出,但是我们能看出来有另外两个函数与他们紧密的关联着:标记需要布局节点的函数markNeedsLayout()和标记需要绘制节点的函数markNeedsPaint()。那么这四个函数有什么关联呢?我们可以看到当markNeedsLayout()函数被调用时_needsLayout属性会被标记为true,而_layoutWithoutResize()函数被调用后,_needsLayout属性又会被重新标记为false,在_needsLayout为true的时候再次调用markNeedsLayout()函数是无效的,直到_layoutWithoutResize()函数被调用,这也就表明了markNeedsLayout()函数先于_layoutWithoutResize()执行;同理,markNeedsPaint()函数先于_paintWithContext()执行。因此我们总结出他们的调用顺序大概为:markNeedsLayout()->_layoutWithoutResize()->markNeedsPaint()->_paintWithContext()。然而实际代码中调用关系可能更为复杂,但是总体是离不开这样一个先后调用机制的。另外,有两个函数我们是可以通过创建RenderObject的子类进行自定义,这两个函数分别是:真正布局的逻辑函数performLayout()和真正绘制逻辑的函数paint(),这也是我们可以自定义组件的一个原因了。
好了,分析到此,最后我们再次进一步总结一下RenderObject对象的作用:
1. RenderObject对象本身是一个节点,并且通过parent属性形成一棵链式节点树,这便于组件之间关系的管理。
2. RenderObject最主要的作用是进行绘制和布局,以此来显示界面。
3. 整棵节点树维系着同一个PipelineOwner对象,该对象管理着需要被布局和绘制节点的集合。
4. RenderObject对象还可以进行点击事件的传递,这让节点具备了可以和用户交互的特性。
通过简要分析,想必大家都了解了RenderObject类的含义和作用了,也明白了为什么RenderObjectWidget类会存在一个创建RenderObject对象的函数createRenderObject()了。那么flutter有没有RenderObject能力的子类呢?这是肯定的,我们来先看一张图:
上图关系稍微有点复杂,一张图片勾画出了flutter中RenderObject类别的总体结构,它们共同构建了flutter中RenderObject的大致体系。如果我们细心观察,不难发现RenderObject体系大概分为两类:具有RenderObjectWithChildMixin特性的一类和具有ContainerRenderObjectMixin以及RenderBoxContainerDefaultsMixin特性的另一类。除了用这两类进行区别,还可以通过RenderSliver和RenderBox来区分RenderObject体系结构。RenderSliver名称有种银装素裹的味道,在其类注释中的说明为:在视图中实现滚动效果的渲染对象的基类,也就是通过RenderSliver我们可以很方便去实现组件的滚动效果,可以滚动的组件总是在某个方向上没有边界定义,因此这个方向就没尺寸大小而言了。而RenderBox名称意为渲染盒子,在其类的注释中的说明为:二维卡迪尔坐标系的渲染对象,既然存在坐标系,那么每个RenderBox就应该有宽高有大小,即有范围有边界。
那么,RenderObjectWithChildMixin和ContainerRenderObjectMixin、RenderBoxContainerDefaultsMixin又有什么区别呢?我们就从代码上进行分析,下图的代码是RenderObjectWithChildMixin类的代码结构:
//1.RenderObjectWithChildMixin只可以被混入到RenderObject的子类中。
//2.被混合的RenderObject子类可以指定ChildType为任意的RenderObject类型。
//3.通过1和2两点可以总结出只要混入RenderObjectWithChildMixin的RenderObject子类
//都可以拥有一个子节点,并且RenderObject具有AbstractNode特性,因此也拥有一个父节点。
mixin RenderObjectWithChildMixin on RenderObject {
//子节点属性
ChildType _child;
ChildType get child => _child;
//为RenderObject的子类增加了一个可以设置子节点的功能。
//改变子节点的值为非空时会先调用到dropChild函数删除旧的子节点对当前节点的依赖,
//再调用到adoptChild让新的子节点依赖当前父节点。为空时,即删除子节点的引用。
set child(ChildType value) {
if (_child != null)
dropChild(_child);
_child = value;
if (_child != null)
adoptChild(_child);
}
//在声明混合结构中可以使用"on"属性来使其具备另一种特性(同时也在限制
//使用混合结构的类型),注释@override表明attach来自RenderObject类中。
@override
void attach(PipelineOwner owner) {
//将owner传递到父节点中(前面的RenderObject类结构中有所展示)
super.attach(owner);
//将owner传递到子节点中
if (_child != null)
_child.attach(owner);
}
//detach来自另外一种RenderObject的特性中,
//其实是来自于RenderObject自有的特性AbstractNode中,
//验证了类的特性具有可传递性。
@override
void detach() {
//删除父节点对owner的引用
super.detach();
//删除子节点对owner的引用
if (_child != null)
_child.detach();
}
//计算孩子的深度
@override
void redepthChildren() {
if (_child != null)
redepthChild(_child);
}
//因为有了属性子节点,所以该函数被简单实现了
@override
void visitChildren(RenderObjectVisitor visitor) {
if (_child != null)
visitor(_child);
}
}
由上图中对RenderObjectWithChildMixin类的代码分析得知,RenderObjectWithChildMixin类的作用是为RenderObject的子类管理子节点的,而RenderObject对象又有着AbstractNode特性,RenderObject同时又管理着一个父节点。因此,只要是混入了RenderObjectWithChildMixin特性的RenderObject子类都拥有一个父节点和一个子节点。我们对比RenderObjectWithChildMixin和AbstractNode的代码可以发现一个小细节:AbstractNode的父属性parent只能进行间接赋值操作,赋值操作唯一发生的地点在adoptChild()和dropChild()两个函数中,而这两个函数的调用点却发生在RenderObjectWithChildMixin代码里面为子节点属性child赋值的操作中:当child被赋予一个有效的值时,旧的子节点的parent被置空,新的子节点的parent被赋予对当前节点的引用。由此可见child和parent的值是连带产生的,触发点在于修改子节点属性child。总而言之,一棵节点树要想同时拥有父节点和子节点被更好管理着,可以混入RenderObjectWithChildMixin代码并继承RenderObject类。
以上我们介绍了RenderObjectWithChildMixin的基本特性,那么ContainerRenderObjectMixin、RenderBoxContainerDefaultsMixin和它又有什么不同呢?ContainerRenderObjectMixin类的代码量相对较多,但是也不是非常多,我将它全部整理出来的目的基于三点:
1. 便于我们对整体的把握。
2. 有利于提高我们阅读代码的能力。
3. 会学到更多组织代码结构的技能。
那么我们先来分析一下ContainerRenderObjectMixin中相关混合类ContainerParentDataMixin的代码:
//该类有两个指针,使用previousSibling来指向前一个RenderObject节点,
//使用nextSibling来指向下一个RenderObject节点
//混入ContainerParentDataMixin的类必须是ParentData的子类
//泛型ChildType是一个RenderObject的子类
mixin ContainerParentDataMixin on ParentData {
//前一个节点
ChildType previousSibling;
//下一个节点
ChildType nextSibling;
//清除指针
//这个函数的作用有两个:
//1.重新指定属性(上一个节点)previousSlibling中的属性parentData的nextSibling引用为当前节点的
// nextSibling
//2.重新指定属性(下一个节点)nextSlibling中的属性parentData的previousSibling引用为当前节点的
//previousSibling
@override
void detach() {
//该函数必须调用,ParentData类强制要求。
//这里如果ParentData的子类混入了多个具有detach()函数的类(该类排列在
//当前mixin的前面),则super.detach()就代表调用的是该类内部的函数。
super.detach();
if (previousSibling != null) {
//重新指定上一个节点中的parentData的属性值nextSibling
final ContainerParentDataMixin previousSiblingParentData =
previousSibling.parentData;
previousSiblingParentData.nextSibling = nextSibling;
}
if (nextSibling != null) {
//重新指定下一个节点中的parentData的属性值previousSibling
final ContainerParentDataMixin nextSiblingParentData =
nextSibling.parentData;
nextSiblingParentData.previousSibling = previousSibling;
}
//清除当前节点的引用
previousSibling = null;
nextSibling = null;
}
}
我们知道RenderObject对象具备一个ParentData类型的parentData属性,而ParentData类中只存在一个detach()函数,detach()函数会在RenderObject类的dropChild(child)函数中会被调用到,也就是说当节点从节点树中移除时会调用到parentData属性的detach()函数。但是ParentData类的detach函数是一个空实现,所以上图中的ContainerParentDataMixin混合类意在为ParentData的子类实现了一个通用逻辑。上图的代码乍一看可能有点难理解,但是反复琢磨就会明白其中所要实现的功能点。我们根据上面的注释再来结合下面一张图来加深一下理解:
图中节点2表示当前节点,节点1是节点2的上一个节点,节点3是节点2的下一个节点。当前节点2的parentData中的previousSibling指针指向节点1,nextSibling指针指向节点3;而节点1的parentData中的nextSibling指针指向节点2,节点3的parentData中的previousSibling指针指向节点2。当节点2从节点关系中移除时,节点1的parentData中的previousSibling会被指向节点3,而节点3的parentData中的nextSibling会被指向节点1,也就如上图中右图中表示的那种形式了。那Sibling有什么作用呢?从Sibling的名称我们知道,Sibling意为兄弟姐妹,也就是说上图中的节点1、节点2、节点3是兄弟姐妹的关系,所以他们的父亲是同一个,而表示节点的RenderObject类中存在一个ParentData类型的parentData属性,由此可见一个继承RenderObject的子类中的parentData属性如果具备ContainerParentDataMixin特性,那么该子类所表示的一个节点下可以拥有多个孩子节点,这些孩子节点是兄弟姐妹的关系。而当从一棵节点树中移除某个节点时,会触发子节点的detach()函数,如果子节点有多个兄弟姐妹节点,那么这些兄弟姐妹节点会按照上图的过程断绝和该子节点的关系。
//1.通过"on"关键字说明ContainerRenderObjectMixin只可以被混入到RenderObject的子类中。
//2.泛型ChildType是一个RenderObject类型的子类。
//3.泛型ParentDataType是一个ContainerParentDataMixin类型的子类
mixin ContainerRenderObjectMixin
> on RenderObject {
//第一个子节点RenderObject
ChildType get firstChild => _firstChild;
ChildType _firstChild;
//最后一个子节点RenderObject
ChildType get lastChild => _lastChild;
ChildType _lastChild;
//子节点的数量
int get childCount => _childCount;
int _childCount = 0;
//将一个子节点插入到子节点关系中
//如果after是已经存在的子节点,那么child将插入到after之后
void _insertIntoChildList(ChildType child, { ChildType after }) {
//从属性parentData中可以找到子节点在兄弟姐妹节点中的关系
final ParentDataType childParentData = child.parentData;
//增加一个子节点的数量
_childCount += 1;
//直接将子节点插入到第一个节点_firstChild前面
//此时新插入的子节点child将变成第一个子节点_firstChild
if (after == null) {
//将新插入子节点的下一个节点指向节点关系中的第一个子节点
childParentData.nextSibling = _firstChild;
if (_firstChild != null) {
//将节点关系中第一个子节点的上一个节点指向新插入的子节点
final ParentDataType _firstChildParentData = _firstChild.parentData;
_firstChildParentData.previousSibling = child;
}
//将节点关系中的第一个子节点重新指向新插入的子节点,此时新插入的子节点
//将替换原来的第一个子节点作为新的第一个子节点
_firstChild = child;
//如果最后一个子节点为空,就将最后一个子节点指向新插入的子节点
_lastChild ??= child;
//将新的子节点插入到已经存在的子节点after之后
} else {
final ParentDataType afterParentData = after.parentData;
//当子节点after为最后一个子节点时
if (afterParentData.nextSibling == null) {
childParentData.previousSibling = after;
afterParentData.nextSibling = child;
_lastChild = child;
//当子节点after不为最后一个子节点时
} else {
//将新的子节点插入到子节点after之后时,需要四步:
//1.将新节点的下一个节点指向节点after的下一个节点(记为节点A)
//2.将新节点的上一个节点指向节点after
//3.将节点A的上一个节点指向新节点
//4.将after节点的下一个节点指向新节点
//第1步
childParentData.nextSibling = afterParentData.nextSibling;
//第2步
childParentData.previousSibling = after;
final ParentDataType childPreviousSiblingParentData =
childParentData.previousSibling.parentData;
final ParentDataType childNextSiblingParentData =
childParentData.nextSibling.parentData;
//第3步
childPreviousSiblingParentData.nextSibling = child;
//第4步
childNextSiblingParentData.previousSibling = child;
}
}
}
//插入子节点到after节点之后
void insert(ChildType child, { ChildType after }) {
adoptChild(child);
_insertIntoChildList(child, after: after);
}
//添加一个新的子节点到节点关系的末尾
void add(ChildType child) {
insert(child, after: _lastChild);
}
//将所有子节点添加到节点关系中
void addAll(List children) {
children?.forEach(add);
}
//从节点关系中移除一个子节点
void _removeFromChildList(ChildType child) {
final ParentDataType childParentData = child.parentData;
//当child为第一个子节点,则将首节点_firstChild重新指向child的下一个节点
if (childParentData.previousSibling == null) {
_firstChild = childParentData.nextSibling;
} else {
//当child不为第一个子节点时,(将child节点的上一个节点记为节点A)节点A
//的下一个节点要重新指向child节点的下一个节点
final ParentDataType childPreviousSiblingParentData =
childParentData.previousSibling.parentData;
childPreviousSiblingParentData.nextSibling = childParentData.nextSibling;
}
//当child为最后一个子节点,则将尾节点_lastChild重新指向child的上一个节点
if (childParentData.nextSibling == null) {
_lastChild = childParentData.previousSibling;
} else {
//当child不为最后一个子节点时,(将child节点的下一个节点记为节点B)将节点B
//的上一个节点重新指向child节点的上一个节点
final ParentDataType childNextSiblingParentData =
childParentData.nextSibling.parentData;
childNextSiblingParentData.previousSibling = childParentData.previousSibling;
}
//清除child节点的关系引用
childParentData.previousSibling = null;
childParentData.nextSibling = null;
//子节点数量减一
_childCount -= 1;
}
//移除一个子节点分两步:
//1.重新调整节点关系,并清除子节点的parentData数据
//2.清除子节点的其他数据
void remove(ChildType child) {
//第一步
_removeFromChildList(child);
//第二步
dropChild(child);
}
//移除所有子节点,主要逻辑时清除所有节点parentData中的引用
void removeAll() {
ChildType child = _firstChild;
while (child != null) {
final ParentDataType childParentData = child.parentData;
final ChildType next = childParentData.nextSibling;
childParentData.previousSibling = null;
childParentData.nextSibling = null;
dropChild(child);
child = next;
}
_firstChild = null;
_lastChild = null;
_childCount = 0;
}
//将子节点child移动到after节点后面
void move(ChildType child, { ChildType after }) {
final ParentDataType childParentData = child.parentData;
if (childParentData.previousSibling == after)
return;
_removeFromChildList(child);
_insertIntoChildList(child, after: after);
//重新布局
markNeedsLayout();
}
@override
void attach(PipelineOwner owner) {
super.attach(owner);
//调用所有子节点的attach()函数
ChildType child = _firstChild;
while (child != null) {
child.attach(owner);
final ParentDataType childParentData = child.parentData;
child = childParentData.nextSibling;
}
}
@override
void detach() {
//这里会调用到parentData的detach()函数(实现逻辑在RenderObject类中),
//所以如果存在多个兄弟姐妹,那么该节点将脱离和他们的关系
super.detach();
//调用所有子节点的detach()函数
ChildType child = _firstChild;
while (child != null) {
child.detach();
final ParentDataType childParentData = child.parentData;
child = childParentData.nextSibling;
}
}
//计算子节点的深度值
@override
void redepthChildren() {
ChildType child = _firstChild;
while (child != null) {
//子节点的深度值都一致
//每个子节点的深度值都是父节点深度值加1
redepthChild(child);
final ParentDataType childParentData = child.parentData;
child = childParentData.nextSibling;
}
}
//访问所有子节点
@override
void visitChildren(RenderObjectVisitor visitor) {
ChildType child = _firstChild;
while (child != null) {
visitor(child);
final ParentDataType childParentData = child.parentData;
child = childParentData.nextSibling;
}
}
//得到子节点的上一个子节点
ChildType childBefore(ChildType child) {
final ParentDataType childParentData = child.parentData;
return childParentData.previousSibling;
}
//得到子节点的下一个字节点
ChildType childAfter(ChildType child) {
final ParentDataType childParentData = child.parentData;
return childParentData.nextSibling;
}
}
从上图的分析中我们知道任何具有ContainerRenderObjectMinXin性质的RenderObject子类表示的节点都可以拥有多个子节点,并且这些子节点都是通过指针的引用关系将他们链式组合在一起。那么flutter为什么对一个节点下的多个子节点使用链式关系表示,而不使用数组表示呢?主要的原因是flutter框架中不同组件的复杂功能特点,对于拥有多节点的组件,为了能便于灵活的控制这些子节点,增删节点和插入节点就会变得频繁,那么链表的数据结构一定是优于数组的。我们还可以看到ContainerRenderObjectMinXin中存在一个指向节点关系中的首节点指针和一个指向节点关系中的尾节点指针,而子节点关系都存在于RenderObject的parentData属性中,通过首节点指针_firstChild指向节点关系中的第一个节点,通过尾节点指针_lastChild指向节点关系中的最后一个节点,所以在上图代码的实现逻辑中需要不断地调整这两个指针的正确指向。好了,有了之前分析的一张节点关系图,这里我就不再画图做分析了,想象不够,例子来凑,大家可以举例类推。
而另外一个混入类RenderBoxContainerDefaultsMixin有什么作用呢,我们从上面的RenderBox的体系结构图中了解到基本上混入RenderObjectWithChildMixin的RenderBox子类都混入了RenderBoxContainerDefaultsMixin。就RenderBoxContainerDefaultsMixin名称而言,似乎在告诉我们它要为RenderBox容器提供默认实现,也就是说只要是拥有多个子节点的RenderBox类都可以混入RenderBoxContainerDefaultsMixin类来具备几个默认实现功能,而想要拥有多个子节点就务必要具有ContainerRenderObjectMinXin特性,因此RenderBoxContainerDefaultsMixin混入类通过“Implements“关键字来继承ContainerRenderObjectMinXin的特性,以防某些继承RenderBox的子类只具备RenderBoxContainerDefaultsMixin特性。而RenderBoxContainerDefaultsMixin混入类究竟提供了哪些默认实现呢,接下来我们再来大概讲解一下混入类RenderBoxContainerDefaultsMixin的代码,这里就不再详细深入RenderBox类中做剖析,在以后的章节中再单独讨论。以下是RenderBoxContainerDefaultsMixin类代码的简单结构:
//通过泛型的继承关系,我们可以了解到:
//1.子节点类型被限制为RenderBox类型
//2.承载子节点关系的parentData属性类型被限制为ContainerBoxParentData类型
mixin RenderBoxContainerDefaultsMixin
>
implements ContainerRenderObjectMixin {
//找到第一个具备getDistanceToActualBaseline()功能的子节点的基线值
double defaultComputeDistanceToFirstActualBaseline(TextBaseline baseline) {
...
//此处代码自行阅读
...
}
//找到具备getDistanceToActualBaseline()功能的子节点中最小的基线值
double defaultComputeDistanceToHighestActualBaseline(TextBaseline baseline) {
...
}
//找到最后一个可以被命中测试的子节点,
//并调用BoxHitTestResult类的addWithPaintOffset()功能去检查该节点是否被命中
bool defaultHitTestChildren(BoxHitTestResult result, { Offset position }) {
...
}
//绘制每个子节点
void defaultPaint(PaintingContext context, Offset offset) {
...
}
//将子节点的链式结构转为list集合
List getChildrenAsList() {
...
}
}
通过上图对 RenderBoxContainerDefaultsMixin类的功能介绍,我们大概了解到它为RenderBox类提供以下三个主要的默认实现:
1. 寻找基线
2. 命中测试
3. 绘制子节点
至此,我们对flutter中RenderObject的体系结构有了更加清晰的认识了,一方面flutter中的RenderObject体系可以按照是否有边界进行区分,另一方面又可以按照是否有多个子节点进行区分,总结如下:
1. 任何继承RenderSliver的子类都具有可滚动的性质,总是在某个方向上没有边界;任何继承RenderBox的子类都可用坐标系表示,存在宽高和边界。
2. 具有RenderObjectWithChildMixin性质的RenderObject子类只有一个子节点;而具有ContainerRenderObjectMinXin性质的RenderObject可以有多个子节点。
经过上面两节的介绍,我们知道了flutter中主要存在三种widget组件,分别为:StatelessWidget、StatefulWidget和RenderObjectWidget。对于StatelessWidget和StatefulWidget这两种组件来说,他们都具有build构建工作的能力;而对于RenderObjectWidget组件来说,它只具有渲染界面的能力。那么他们是否有共同的特性呢?答案不可置否。我们从他们类的结构中了解到他们都拥有一个共同的函数createElement(),该函数在他们的父类Widget中返回的是一个Element类型的对象,那么本节我们所要讨论的就是flutter中的元素Element,下面我们先来看一张Element系列的类结构图:
通过上图对Element系列类继承结构的分析,我们知道所有Element都继承自BuildContext类,它们主要分为两类:RenderObjectElement类和ComponentElement类。RenderObjectElement意为渲染对象的元素,跟前一章讨论的RenderObject相关;ComponentElement意为组合的元素,其下有两个我们熟悉的子类:StatefulElement和StatelessElement。根据前两章的分析,我们认识到StatefulWidget对象创建的元素为StatefulElement类型,StatelessWidget对象创建的元素为StatelessElement类型,并且RenderObjectWidget创建的元素为RenderObjectElement类型。下面我们来认识一下它们的祖先Element类,Element类具有BuildContext类的特性,我们就先从BuildContext类入手,来了解一下类中定义了那些功能:
//此处只做简单的注释说明
abstract class BuildContext {
//保存元素的配置
Widget get widget;
//管理着连接元素的显示管道
BuildOwner get owner;
//寻找渲染对象
RenderObject findRenderObject();
//保存的是通过findRenderObject()返回的RenderBox的大小
Size get size;
//向ancestor注册当前上下文
InheritedWidget inheritFromElement(InheritedElement ancestor, { Object aspect });
//向与targetType关联的ancestor注册当前上下文
InheritedWidget inheritFromWidgetOfExactType(Type targetType, { Object aspect });
//获取与targetType关联的ancestor
InheritedElement ancestorInheritedElementForWidgetOfExactType(Type targetType);
//获取类型为targetType的父元素的配置widget
Widget ancestorWidgetOfExactType(Type targetType);
//获取(与matcher子类指定的状态类型相同的)最接近的父元素的状态state
State ancestorStateOfType(TypeMatcher matcher);
//获取(与matcher子类指定的状态类型相同的)最后面的父元素的状态state
State rootAncestorStateOfType(TypeMatcher matcher);
//遍历所有直接子元素(非间接子元素不参与遍历)
void visitChildElements(ElementVisitor visitor);
}
通过对上图BuildContext类的分析,我们知道该类保存了一个配置组件widget和一个连接显示管道的BuildOwner,并且还具有寻找渲染对象的的功能。BuildContext似乎将上两节介绍的功能全部关联到了一起,而所有的Element都具有BuildContext类的特性,所以通过控制元素对象即可操作flutter中组件从构建到显示的过程。BuildContext类还具有获取RenderBox对象尺寸的方法,但是我们知道想要获取尺寸大小,必须得在组件布局完成后才能获取。另外BuildContext类还具有其他的几个功能,这些功能的作用我们再在下面Element类中寻找答案吧,下面是Element类的代码结构:
//"implement"关键字表明所有元素都具备BuildContext性质
abstract class Element extends DiagnosticableTree implements BuildContext {
//元素创建时即关联一个widget配置
Element(Widget widget)
: _widget = widget;
//保存指向父元素的引用
Element _parent;
//将当前元素与other对象进行比较,通过identical()函数确定两个对象是否同一个
@override
bool operator ==(Object other) => identical(this, other);
//获取元素的哈希值
@override
int get hashCode => _cachedHash;
//定义元素的哈希算法(范围: 0~16777214)
final int _cachedHash = _nextHashCode = (_nextHashCode + 1) % 0xffffff;
static int _nextHashCode = 1;
//父元素设置的信息,可以确定子元素在父级中的位置
dynamic get slot => _slot;
dynamic _slot;
//保存元素的深度值
int get depth => _depth;
int _depth;
//定义元素在元素集合中的排序算法,优先根据元素的
//深度值排序,再根据元素的属性dirty进行排序,如下:
//1.深度值小的排在前面
//2.dirty为false的排在前面
static int _sort(Element a, Element b) {
if (a.depth < b.depth)
return -1;
if (b.depth < a.depth)
return 1;
if (b.dirty && !a.dirty)
return -1;
if (a.dirty && !b.dirty)
return 1;
return 0;
}
//保存元素的配置组件
@override
Widget get widget => _widget;
Widget _widget;
//1.管理元素的构建过程
//2.管理非活动元素
//3.连接元素的显示通道
@override
BuildOwner get owner => _owner;
BuildOwner _owner;
//元素是否为活动状态
bool _active = false;
//当前元素是否为脏的状态,默认为true
bool get dirty => _dirty;
bool _dirty = true;
//当前元素是否在脏元素集合中,默认为false
bool _inDirtyList = false;
//向下搜寻渲染对象
RenderObject get renderObject {
RenderObject result;
void visit(Element element) {
if (element is RenderObjectElement)
result = element.renderObject;//如果是渲染对象,renderObject函数会被重写
else
element.visitChildren(visit);//继续向下搜寻子元素的渲染对象
}
visit(this);//从元素本身开始搜寻
return result;
}
//更新子元素的配置,返回一个拥有新配置的子元素
//参数:
//child - 子元素
//newWidget - 子元素新配置
//newSlot - 子元素的位置插槽(可以确定子元素在父元素中的位置)
@protected
Element updateChild(Element child, Widget newWidget, dynamic newSlot) {
//如果新的配置为空,则返回一个空元素
if (newWidget == null) {
if (child != null)
deactivateChild(child);//将子元素变为非活动元素
return null;
}
//如果新配置和子元素都不为空
if (child != null) {
//如果子元素的旧配置和新配置是同一个引用对象
if (child.widget == newWidget) {
if (child.slot != newSlot)//如果位置插槽不同,则只更新子元素的插槽值
updateSlotForChild(child, newSlot);
return child;
}
//如果子元素的旧配置和新配置的引用对象不同,并且如果canUpdate()返回true,即:
//1.旧配置的类型和新配置类型一致
//2.旧配置的key和新配置的key一致
if (Widget.canUpdate(child.widget, newWidget)) {
if (child.slot != newSlot)
updateSlotForChild(child, newSlot);//如果位置插槽不同,则更新子元素的插槽值
//将子元素的旧配置替换为新配置
child.update(newWidget);
return child;
}
//如果子元素的旧配置和新配置的引用对象不同,并且
//如果旧配置的类型和新配置类型不一致或旧配置的key和新配置的key不一致时,
//将子元素变为非活动元素
deactivateChild(child);
}
//当子元素为空或子元素变为非活动元素时,
//使用inflateWidget()函数去获取一个已经安装完成的子元素
return inflateWidget(newWidget, newSlot);
}
//获取一个已经安装完成的子元素:
//1.能复用尽量优先复用对象
//2.不能复用即创建一个新的元素对象
@protected
Element inflateWidget(Widget newWidget, dynamic newSlot) {
//获取新配置的key
final Key key = newWidget.key;
//如果key为GlobalKey类型即可尝试复用对象
if (key is GlobalKey) {
//从元素注册表中尝试获取一个元素对象进行复用
final Element newChild = _retakeInactiveElement(key, newWidget);
if (newChild != null) {
//1.将当前元素和被复用元素建立父子关系
//2.更新当前元素的深度值
//3.将当前元素变为活动元素
//4.更新当前元素的插槽位置
newChild._activateWithParent(this, newSlot);
//再调用updateChild()更新被复用元素的配置及插槽值
final Element updatedChild = updateChild(newChild, newWidget, newSlot);
//返回被复用元素作为当前元素的子元素
return updatedChild;
}
}
//未复用到元素对象就创建一个新元素对象
final Element newChild = newWidget.createElement();
//安装子元素
newChild.mount(this, newSlot);
//返回子元素
return newChild;
}
//此处传入的是:新配置widget的key和新配置widget
Element _retakeInactiveElement(GlobalKey key, Widget newWidget) {
//元素注册表保存在GlobalKey类中,从注册表中获取跟当前key相关联的元素
//注意:当元素已经安装过(元素创建时)才会注册到GlobalKey中,否则此处返回null。
//也就是说如果任何一个已经创建过的元素的key和新配置的key一致,即可尝试复用该元素
final Element element = key._currentElement;
//元素为空时,即无法复用
if (element == null)
return null;
//如果新配置的key和被复用元素element中widget的key不一致或者
//被复用元素element中widget的类型和新配置的类型不一致,则返回null
if (!Widget.canUpdate(element.widget, newWidget))
return null;
//删除被复用元素父子的相互引用关系以及其他对象
final Element parent = element._parent;
if (parent != null) {
parent.forgetChild(element);
parent.deactivateChild(element);
}
//将被复用元素从非活动元素集中移除
owner._inactiveElements.remove(element);
//返回被复用的元素
return element;
}
//将子元素变为非活动元素
@protected
void deactivateChild(Element child) {
//删除子元素对父元素的引用
child._parent = null;
//清除子元素的渲染对象
child.detachRenderObject();
//将子元素添加到非活动元素集中进行管理
owner._inactiveElements.add(child);
}
//清除子元素的渲染对象
//此处会向下清除(具有渲染对象的)子元素
void detachRenderObject() {
visitChildren((Element child) {
child.detachRenderObject();
});
//将插槽值置空
_slot = null;
}
//意为忘掉子元素,具体交由不同类型的Element实现
@protected
void forgetChild(Element child);
//向下更新子元素的插槽值
//这里表明所有子元素的插槽值都一致
@protected
void updateSlotForChild(Element child, dynamic newSlot) {
//内部函数可以访问外部newSlot
void visit(Element element) {
//更新元素的插槽值
element._updateSlot(newSlot);
if (element is! RenderObjectElement)
element.visitChildren(visit);//非RenderObjectElement继续向下更新插槽值
}
visit(child);
}
//更新元素的插槽值
void _updateSlot(dynamic newSlot) {
_slot = newSlot;
}
//更新元素的深度值
void _updateDepth(int parentDepth) {
//期望元素的深度值为父元素深度值加1
final int expectedDepth = parentDepth + 1;
//如果元素的深度值小于期望值,则进行更新(这里表明父元素的深度值永远小于子元素的深度值)
if (_depth < expectedDepth) {
//将深度值更新为期望值
_depth = expectedDepth;
//重新计算子元素的深度值
visitChildren((Element child) {
child._updateDepth(expectedDepth);
});
}
}
//更新元素的配置
@mustCallSuper
void update(covariant Widget newWidget) {
_widget = newWidget;
}
//向下附加子元素的渲染对象,并且配置新的插槽值
//这里表明了渲染对象和插槽值相关
void attachRenderObject(dynamic newSlot) {
visitChildren((Element child) {
child.attachRenderObject(newSlot);
});
_slot = newSlot;
}
//将当前元素变为活动元素
void _activateWithParent(Element parent, dynamic newSlot) {
//将当前元素的父引用指向新的父元素
_parent = parent;
//此处会向下循环调用_updateDepth()函数重新计算当前元素及其下所有子元素深度值
_updateDepth(_parent.depth);
//此处会向下循环调用activate()函数使当前元素及其下所有子元素都变为活动元素
_activateRecursively(this);
//此处会向下循环调用attachRenderObject()函数附加当前元素及其下所有子元素的渲染对象
attachRenderObject(newSlot);
}
//使当前元素及其下所有子元素都恢复为活动元素
static void _activateRecursively(Element element) {
//当前元素及其下所有子元素都会递归的调用activate()函数
element.activate();
element.visitChildren(_activateRecursively);
}
//使当前元素变为活动元素
@mustCallSuper
void activate() {
//查看当前元素的依赖状态,以下任意一种情况为真都需要重新调用didChangeDependencies()函数:
//1. 旧的依赖集合_dependencies不为空
//2. 旧的_hadUnsatisfiedDependencies属性为true
final bool hadDependencies = (_dependencies != null && _dependencies.isNotEmpty)
|| _hadUnsatisfiedDependencies;
//设置活动属性为true
_active = true;
//清除旧的依赖集合使其变为空
_dependencies?.clear();
//重新将_hadUnsatisfiedDependencies属性设置为false
//该属性意为是否还存在未处理的依赖情况
_hadUnsatisfiedDependencies = false;
//主要是将父元素的_inheritedWidgets传递到当前子元素的属性上
_updateInheritance();
//如果当前元素为脏,即_dirty为true,就调用scheduleBuildFor重新安排构建任务
if (_dirty)
owner.scheduleBuildFor(this);
//依赖发生改变后,就重新调用didChangeDependencies(),以下任意一种情况
//1. _dependencies从非空变为空
//2. _hadUnsatisfiedDependencies从true变为false
if (hadDependencies)
didChangeDependencies();
}
//使当前元素变为非活动元素
@mustCallSuper
void deactivate() {
if (_dependencies != null && _dependencies.isNotEmpty) {
//从依赖的所有元素中注销对当前元素的引用
for (InheritedElement dependency in _dependencies)
dependency._dependents.remove(this);
}
//删除从旧的父元素中继承的_inheritedWidgets
_inheritedWidgets = null;
//设置活动属性为false
_active = false;
}
//从父元素继承_inheritedWidgets属性
void _updateInheritance() {
_inheritedWidgets = _parent?._inheritedWidgets;
}
//改变元素的依赖后,尝试调用markNeedsBuild重新构建元素
@mustCallSuper
void didChangeDependencies() {
markNeedsBuild();
}
//该函数只为非活动状态并且不为脏的元素安排构建任务
void markNeedsBuild() {
//元素是活动状态才允许安排构建
if (!_active)
return;
//如果为脏就不需要再重新构建,意味着该函数只构建不为脏的元素
if (dirty)
return;
//如果不为脏,重新设置为脏安排构建任务
_dirty = true;
//这地方也说明只有当元素为脏scheduleBuildFor构建任务才起作用
owner.scheduleBuildFor(this);
}
//重新构建元素
void rebuild() {
//非活动元素或不为脏的元素不需要重新构建
if (!_active || !_dirty)
return;
//调用子类的performRebuild重新构建元素
performRebuild();
}
//该函数具体交由子类完成,不同类型的Element具体的构建方式不一样
@protected
void performRebuild();
//当元素被创建时需要立即安装元素对象
@mustCallSuper
void mount(Element parent, dynamic newSlot) {
//建立父子关系
_parent = parent;
//设置新的插槽值
_slot = newSlot;
//计算元素的深度值
_depth = _parent != null ? _parent.depth + 1 : 1;
//使元素变为活动元素
_active = true;
//传递父元素的构建管理对象
if (parent != null)
_owner = parent.owner;
//将新的元素注册到GlobalKey的元素注册表中
if (widget.key is GlobalKey) {
final GlobalKey key = widget.key;
key._register(this);
}
//从父元素继承遗产_inheritedWidgets属性
_updateInheritance();
}
//当元素被销毁时需要立即卸载元素对象
//当然非活动元素和可被复用的元素不会被卸载
@mustCallSuper
void unmount() {
if (widget.key is GlobalKey) {
final GlobalKey key = widget.key;
key._unregister(this);
}
}
//该类在Element中为空实现,具体交由拥有不同子元素结构的Element的子类实现
void visitChildren(ElementVisitor visitor) { }
//----以下的所有函数全部来自BuildContext类中----
//我们详细看看每个功能在元素Element类中的具体实现
//寻找渲染对象
@override
RenderObject findRenderObject() => renderObject;
//寻找RenderBox的尺寸大小
@override
Size get size {
//寻找渲染对象
final RenderObject renderObject = findRenderObject();
//如果渲染对象为有边界特性的RenderBox,则返回它的大小
if (renderObject is RenderBox)
return renderObject.size;
//没有找到RenderBox就返回null
return null;
}
//该_inheritedWidgets永远是来自父元素的遗产
//也就是说整棵元素树只维护一个_inheritedWidgets对象
//每一种Type类型对应一种InheritedElement元素
Map _inheritedWidgets;
//依赖关系集合,保存了Element在活动状态时的所有依赖的InheritedElement元素
Set _dependencies;
//是否存在未满足的依赖
//决定是否需要重新安排构建任务的一个属性因素
bool _hadUnsatisfiedDependencies = false;
//主要是将当前元素和ancestor对象建立关系
//1. 将ancestor对象添加到当前元素的依赖集合中
//2. 将当前元素更新到ancestor对象的注册表中
@override
InheritedWidget inheritFromElement(InheritedElement ancestor, { Object aspect }) {
//依赖集合为null就创建它
_dependencies ??= HashSet();
//第一步
_dependencies.add(ancestor);
//第二步
ancestor.updateDependencies(this, aspect);
//返回ancestor对象的配置组件
return ancestor.widget;
}
//该函数主要是从_inheritedWidgets属性中获取需要的InheritedElement,添加到自己的依赖中
@override
InheritedWidget inheritFromWidgetOfExactType(Type targetType, { Object aspect }) {
//1. 从_inheritedWidgets中获取一种targetType类型对应的元素
//ancestor元素注册表中可能还关联着整棵元素树中的许多其他元素
final InheritedElement ancestor = _inheritedWidgets == null ?
null : _inheritedWidgets[targetType];
//2. 如果存在这样的元素就调用inheritFromElement函数将当前元素和ancestor建立联系
if (ancestor != null) {
return inheritFromElement(ancestor, aspect: aspect);
}
//3. 不存在这样的元素,则期望得不到满足,即_hadUnsatisfiedDependencies值为true
//下次从非活动元素恢复为活动元素时会重新安排构建任务
_hadUnsatisfiedDependencies = true;
return null;
}
//直接从_inheritedWidgets中获取一个targetType类型的InheritedElement对象
@override
InheritedElement ancestorInheritedElementForWidgetOfExactType(Type targetType) {
final InheritedElement ancestor = _inheritedWidgets == null ? null :
_inheritedWidgets[targetType];
return ancestor;
}
//找到第一个匹配的父元素的配置组件
//条件:具有targetType类型的配置组件
@override
Widget ancestorWidgetOfExactType(Type targetType) {
Element ancestor = _parent;
while (ancestor != null && ancestor.widget.runtimeType != targetType)
ancestor = ancestor._parent;
return ancestor?.widget;
}
//找到第一个匹配的父元素的状态
//条件:拥有被matcher指定类型匹配的状态
@override
State ancestorStateOfType(TypeMatcher matcher) {
Element ancestor = _parent;
while (ancestor != null) {
if (ancestor is StatefulElement && matcher.check(ancestor.state))
break;
ancestor = ancestor._parent;
}
final StatefulElement statefulAncestor = ancestor;
return statefulAncestor?.state;
}
//找到最后一个匹配的父元素的状态
//条件:拥有被matcher指定类型匹配的状态
@override
State rootAncestorStateOfType(TypeMatcher matcher) {
Element ancestor = _parent;
StatefulElement statefulAncestor;
while (ancestor != null) {
if (ancestor is StatefulElement && matcher.check(ancestor.state))
statefulAncestor = ancestor;
ancestor = ancestor._parent;
}
return statefulAncestor?.state;
}
//找到第一个匹配的父元素的渲染对象
//条件:拥有被matcher指定类型匹配的渲染对象
@override
RenderObject ancestorRenderObjectOfType(TypeMatcher matcher) {
Element ancestor = _parent;
while (ancestor != null) {
if (ancestor is RenderObjectElement && matcher.check(ancestor.renderObject))
break;
ancestor = ancestor._parent;
}
final RenderObjectElement renderObjectAncestor = ancestor;
return renderObjectAncestor?.renderObject;
}
//访问当前元素的每一个(包括直接或间接的)父级元素
@override
void visitAncestorElements(bool visitor(Element element)) {
Element ancestor = _parent;
while (ancestor != null && visitor(ancestor))
ancestor = ancestor._parent;
}
//访问每一个(直接的)子元素
@override
void visitChildElements(ElementVisitor visitor) {
visitChildren(visitor);
}
}
以上我们对Element类结构做了具体的分析,我们知道每一种元素都拥有各自的配置属性(Widget)和一个构建管理对象(BuildOwner)。生产一个元素(创建元素或者复用元素)需要依赖于具体的配置组件widget,而构建管理对象BuildOwner具有两个作用:1.管理非活动元素。2.安排构建任务。我们着重看元素Element类中比较重要的三个函数:mount(Element parent, dynamic newSlot) 、rebuild()以及updateChild(Element child, Widget newWidget, dynamic newSlot)。当元素在第一次被创建时会调用安装函数mount(Element parent, dynamic newSlot),元素被安装后,该元素就被添加到一棵元素树中,此时元素的_active被设置为true变为活动状态,而元素的_dirty默认是true为脏的状态。
大家自行查看ComponentElement类和RenderObjectElement类的实现会发现如下两点情况:
1. 如果元素类型为ComponentElement类型,那么当元素同时为活动状态和脏的状态时,此时需要调用rebuild()函数重新构建元素,而元素具体的构建任务交由performRebuild()函数实现,构建的目的是使用新的配置组件widget构建出当前元素的子元素,每种不同的元素Element的子类实现的子元素结构不一样。然而要想构建出新的子元素,势必会调用到元素类Element中的函数updateChild(Element child, Widget newWidget, dynamic newSlot),因此,在performRebuild()函数中一定会调用到updateChild(Element child, Widget newWidget, dynamic newSlot)函数来使用新配置创建或者更新元素的子元素。
2. 如果元素为RenderObjectElement类型,那么情况就有所不同,在安装元素时会创建配置组件中的渲染对象,并将它添加到渲染对象树中,其实插入时只触发了一个getter函数"get child",却做了一系列的事(请看第二节对渲染对象RenderObject的介绍)。另外,使用新配置创建或更新子元素的逻辑一定也是在安装时完成,即使父类中找不到,子类中务必会进行实现。注意虽然RenderObjectElement不会在安装时调用元素的构建函数rebuild,但也会在元素_active和_dirty状态同时为真时进行调用,而performRebuild()函数只对渲染对象进行了更新操作。
另外,我们知道构建管理对象BuildOwner和上节介绍的PipelineOwner一样会贯穿整棵元素树,因为该对象(元素安装时)是从其当前元素的父元素中继承而来。如果元素的配置组件widget拥有key值,那么该元素对象就有被其他配置组件在创建元素时被重用的可能,因为拥有key配置组件的元素在会在安装时被注册进GlobalKey的注册表中。
我们再谈谈元素中来自BuildContext类中的其他几个函数的实现,这里只说明一下这两个函数:inheritFromElement(InheritedElement ancestor, { Object aspect })、inheritFromWidgetOfExactType(Type targetType, { Object aspect })。这两个函数主要与_inheritedWidgets和_dependencies这两个属性相关联,从_updateInhritance()函数实现中得知属性_inheritedWidgets由父元素继承而来,因此贯穿整棵元素树。而_dependencies属性在每个元素对象中都拥有一份。由此可知_inheritedWidgets中保存的是整棵元素树中所有元素依赖的共同配置,而_dependencies中保存的是当前元素的依赖元素(每个依赖元素都拥有一份配置)。inheritFromElement(InheritedElement ancestor, { Object aspect })函数主要功能是将一个新的可配置元素ancestor添加进当前的依赖集合_dependencies中,并将自身注册进ancestor配置元素的注册表中进行关联,一旦依赖配置发生改变,那么注册表内的所有元素都要进行相应的更新(比如主题的配置由红色背景变为蓝色背景),这是一个观察者模式的应用,被观察的主题对象为ancestor,而观察者就是这些需要跟随主题做出改变的整棵元素树中的某些元素了。再看inheritFromWidgetOfExactType(Type targetType, { Object aspect })函数,该函数主要是获取某种类型的元素的配置,一棵元素树中一种类型只会存在一个元素对象,如果获取到某个已存在的依赖元素,就会通过inheritFromElement函数添加进当前元素的依赖集中。所以_dependencies中的配置元素永远来自于_inheritedWidgets的全局树的依赖集合中。
分析到此,下面我们来总结一下元素Element的几大特性:
1. 每个元素对象中都拥有一份可配置的组件widget,生产元素的方式由元素的配置组件决定
2. 每个元素在创建时都需要进行安装才能被使用
3. 带有key配置的元素具有被复用的能力
4. 活动的元素为脏状态时需要被重新构建
5. 每个元素都可由活动状态变为非活动状态,被重用时也可由非活动状态恢复为活动状态
6. 每个元素都可跟随元素树中全局的依赖配置做出更新
7. 每个元素都拥有一个贯穿整棵元素树的构建管理对象BuildOwner
不知篇幅是否合适,本章我们暂且讨论到此,下一章中我再来带大家具体分析一下flutter框架中的两大管理对象BuildOwner和PipelineOwner,以及flutter中的各种Binding及其他相关类,本篇纯属个人见解,如有不足,还望大家多多批评。最后,感谢大家的认真阅读,希望各位网友在阅读后能有所收获,以此。