刚刚看完张风捷特烈的Flutter 布局探究小册。感觉获益良多。
看到结局的问题:怎么区分StatelessWidget 和 StatefulWidget 的运用场景,不由开端自问,关于StatefulWidget ,StatelessWidget,以及flutter中Widget的众多子类我真的足够了解吗?
关于自己常常要打交道的东西,假如仅仅一知半解则不利于进步。
下面就从源码的角度来学习下flutter根底的几个Widget 都起到了什么效果。
先给个简略总结:
- 其间StatelessWidget 和 StatefulWidget 起到了组织组合子组件的效果。
- RenderObjectWidget 起到烘托效果。包括制作偏移和测量信息。
- ProxyWidget 可以带着信息,以供其他组件运用。
一、探究StatelessWidget的组件构建
在运用StatelessWidget的时分,一般只需求完结一个build办法。就拿咱们常用的Container组件举例,他便是StatelessWidget 的子类。他的build办法回来的便是各种组件的组合嵌套。

他的各种成员属性也仅仅用来配置子组件的组合办法罢了。
1. StatelessWidget 的build调用机遇,以及widget树遍历流程
Container组件是StatelessWidget的经典子类。
咱们经过断点调试看看Container 组件build办法的调用堆栈
在ComponentElement 的performRebuild 办法调用的时分,触发了build办法,从stateless中获取了build回来的Widget,而又在performRebuild 调用了updateChild办法,对一切的后代Element进行build遍历。
ComponentElement是Widget对应元素StatelessElement和StatefulElement的父类。
咱们拉到开端的调用栈。Element栈调用的起点在于attachRootWidget办法。
还记得咱们flutter app开发的起点吗?便是runApp(App())办法,开启了整个flutter app。
attachRootWidget办法正是咱们在调用runApp的时分履行的。
在其间,履行了RenderObjectToWidgetAdapter组件的初始化,将renderView 和 rootWidget作为入参。并且调用attachToRenderTree回来元素树极点的Element。
三颗树的极点
其间renderView是RenderObject树的极点,_renderViewElement是Element树的极点。匿名的RenderObjectToWidgetAdapter则是Widget树的极点,可是他没有被引用。Widget树的保护依赖于Element树,rootWidget便是咱们的runApp组件节点,被作为参数挂载到RenderObjectToWidgetAdapter根组件中,被后续的Element挂载循环运用。
Element中也存放了_parent变量,所以咱们经过Element目标可以轻松的追溯到先人节点。
咱们从上面的剖析可以得出ComponentElement 的 performRebuild办法是element.build传承要害办法 ,mount办法也能由此挂载出一切子树(其他类型的Element完结方案略有不同)
在ComponentElement中。也由performRebuild构建出一层层的后代节点。代码如下,留意红色方框的代码。
第一个红框中是build()办法的履行。意味着每次performRebuild被调用的时分,子组件都会被build出来,由此可知widget是仅有的,每次更新都会有新的Widget生成。
在updateChild的过程中,假如子element还未生成,就会调用widget.createElement()办法取得element 。
咱们再看StatelessWidget 的源码,完结了createElement办法回来了自界说的StatelessElement。
生成的子Element 都会在ComponentElement中被持有,以便后续更新
由此可知,ComponentElement维系了祖孙联系,其子类Element对应的 StatelessWidget,StatefulWidget,ParentDataWidget 和 InheritedWidget都天然拥有后代联系才能。
如下所示,StatefulElement 是 ComponentElement 的子类。

2. StatelessWidget 和Element在烘托中的更新
widget的创立都是在element树遍历的过程中履行的。
widget树依赖于element树,在Element创立的时分widget实例将会被持有。
StatelessWidget在布局和烘托流程中依赖Element维系,树联系被Element发掘。

在Element performeRebuild从头构建的时分,有一个是否更新Element的断定机制,以优化功能。
不管是更新update还是挂载mount,每次子widget都会先build()出来。再进行新旧比较。Widget都是一次性的,假如有状况需求保存是由其他办法完结的。
咱们再看updateChild办法。上面一末节说到在子element为空的时分,会在其间createElement。而在子Element不为空的时分,会依据新旧Widget 的不同,进行不同的操作。
其间经过新旧widget的equals断定。决定是否复用之前的element。假如复用了element,依据canUpdate办法的回来值,来履行child.update办法。所以咱们可以得出这样一个定论。
widget 的 canUpdate 完结,将很大程度上决定 Element 的复用。削减从头制作,对State从头赋值,甚至状况丢失的资源浪费。
3. 探究key的效果
canUpdate的默许完结中以Widget的类型和key作为要害字进行判断。假如有对key界说,那么Key的共同性就会对widget的更新显得尤为要害。
这也是咱们在做功能优化的时分需求留意的。可以运用Key的配置,来控制组件是否需求更新。
static bool canUpdate(Widget oldWidget, Widget newWidget) {
return oldWidget.runtimeType == newWidget.runtimeType
&& oldWidget.key == newWidget.key;
}
Key的几种子类基本上都是依据需求,对== 操作符做不同的完结。以更好的自界说 canUpdate 的成果。
其间GlobalKey比较特殊。作为大局的仅有秘钥。供给了对应 widget 的 BuildContext 和 widget 的拜访办法。并针对 StatefulWidget。还供给了 State 的拜访。
以便用户对状况进行大局的更新。比方咱们需求在外部运用 BuildContext 进行初始化的时分,可以进行这样调用
4. 小结
经过以上对StatelessWidget和ComponentElement 的剖析,可以得出以下的判断。
StatelessWidget 根据 ComponentElement。首要功能便是供给了组合各种widget的才能,并维持了祖孙的build传承。
当然在探究傍边也发现了一些技术债款,由于咱们现已知道了statelesswidget的运用场景,关于详细的源码细节先按下不表,在此只记载
- 生命周期_lifecycleState 起到什么效果
- _dirty 标记和 markNeedsBuild 的用法和原理是什么
- BuildOwner 的效果是什么
二、探究StatefulWidget的动态改写机制
StatefulWidget 和 StateflessWidget 有很多共同之处。最首要的原因便是他们创立的元素都是ComponentElement的子类,其供给了widget后代build传承的才能。
可知StatefulWidget和StateflessWidget相同,也是一个有才能组合各种widget的组件。
1. State生命周期剖析
StatefulWidget 界说了createState办法。供给了状况改写才能。

再次从StatefullElement的build办法入手。直接调用了state.build(this)。代理了state的构建行为。
在performRebuild办法中也进行了state.didChangeDependencies生命周期回调。
在State中,除了生命周期办法外, 最重要的便是build办法了。效果和StatelessWidget的build办法共同。都是供给了组合widget的才能。
initState则给用户供给了初始化state状况的机会。断点调试看看调用栈怎么。
调试中直观看到,在firstBuld的时分,state的initState被调用。并在之后调用了didChangeDependencies生命周期办法,和build办法。
代码中也对办法做了限制,不可以回来Future类型。 所以咱们可以在initState中放心做一些初始化作业,没有异步参加,作业将会在build之前完结。
2. setState办法改写页面办法剖析
关于setState办法。除开生命周期的判断之外,要害代码只要一句,便是调用了element 的markNeedsBuild()

该办法将对应的element标记为dirty。并且调用owner``!.scheduleBuildFor(``this``);将其加入到 BuildOwner的脏列表(_dirtyElements)中。
将会在下次帧改写的时分调用BuildOwner.owner.buildScope 从头构建该列表中的元素。
3. 小结
StatelessWidget给运用者供给了一个快捷的布局改写进口,咱们可以运用setState改写布局。该办法会将对应Element标记为待改写元素,在下次帧改写的时分重建布局。状况的改动将会被重建的布局从头获取。
三、探究SingleChildRenderObjectWidget
SingleChildRenderObjectWidget对应的元素类是SingleChildRenderObjectElement。
咱们作为开发者,布局过程中SingleChildRenderObjectWidget 的子类运用频率非常频繁,布局的束缚,偏移和烘托都是由RenderObjectWidget 完结的,SingleChildRenderObjectWidget继承了RenderObjectWidget的烘托才能,并供给了单子传承的才能。布局的过程中该目标的子类不可或缺,flutter框架中也有不少对应的完结类。
Flutter 框架中完结的SingleChildRenderObjectWidget有以下几种。
- SizedBox
- LimitedBox
- ShaderMask
- RotatedBox
- SizedOverflowBox
- Padding
- …
1. 探究SingleChildRenderObjectElement中关于子widget的挂载和更新
SingleChildRenderObjectElement`的`mount` 和 `update`办法都很简略,都是直接调用了`updateChild`办法,传进去的子widget直接是`widget.child
这个办法和ComponentElement基本上相同,都是运用canUpdate的成果进行更新或者是创立子Element。
1. 以Padding为例了解RenderObjectWidget 的布局和制作完结。
名词解释
RenderObject:烘托目标,flutter目标布局的束缚,制作,位移满是由该目标完结,RenderObject树的祖孙中传递着束缚,以做到布局巨细的传承影响。
RenderObject的创立
RenderObjectWidget 会在mount挂载的时分,创立RenderObject,直接调用widge.createRenderObject。咱们的束缚,制作,位移满是由RenderObject传递和完结的。
RenderPadding的布局完结
以Padding为例。createRenderObject创立了RenderPadding实例,widget的成员原封不动交给了该实例。
束缚(BoxConstraint)是Flutter确认布局巨细的方案,各种RenderObject关于束缚的传递都有自己的完结。
下方是RenderPadding的performLayout代码。红框标记起来的代码中就展现了Padding的束缚传承逻辑。
其父布局传给自己束缚根底上减去Padding再传递给子RenderObject。
调查performLayout办法可以发现,该办法完结了束缚的传递,计算了偏移量Offset,并确认了自己的巨细。
确认巨细束缚之后,就会在paint中制作自己和后代。RenderPadding没有自界说制作,直接运用了父类RenderShiftedBox的完结。RenderShiftedBox 供给了offset偏移。在制作子renderObject的时分,为其施加制作偏移量。有些需求计算子布局偏移的widget,如Padding,Align等,都对RenderShiftedBox进行了完结。

可以看到子布局的offset存在他的parentData中。PaddingRender运用的parentData是BoxParentData,内部供给了offset变量以供父布局运用。
/// Parent data used by [RenderBox] and its subclasses.
class BoxParentData extends ParentData {
/// The offset at which to paint the child in the parent's coordinate system.
Offset offset = Offset.zero;
@override
String toString() => 'offset=$offset';
}
一切的RenderBox都持有BoxParentData目标,用于存储位移信息,在setUpPrentData的时分进行的初始化。红框中的代码展现了这一细节。
到此,就能了解RenderObject是怎么被束缚BoxConstraint,怎么被布局layout,以及怎么被制作paint。
1. RenderObjectElement的传承办法
RenderObjectElement 的父子传承在两个子类中完结,在第1小结中现已说到SingleChildRenderObjectWidget 和ComponentElement非常相似,仅仅直接把widget.child拿来传承,而不再供给build办法以供子组件组合。
MultiChildRenderObjectElement 也相似,只不过作为多子组件,三棵树分叉的首要因子,保护的是children 列表。

在mount 和 update 的时分,后代组件会像点了爆竹相同被逐一构建和更新。
1. 小结
每个SingleChildRenderObjectWidget组件都完结了各自的布局和制作方案,也各自处理了束缚并传递下去。
比方ColordBox作为制作组件,借助了RenderColord,制作了自身色彩,束缚则取得是父束缚的最小值。Align作为定位组件,借助了RenderPositionedBox,布局的时分计算了对应的偏移量offset,在制作子布局的时分运用,束缚则在传递的时分转了松束缚。
诸如此类,一切组件都运用了对应的RenderObject满意了各自布局和烘托的一切需求。咱们自己当然也可以自界说对应的RenderObject完结自己的布局。
MultiChildRenderObjectWidget 和 SingleChildRenderObjectWidget相似,仅仅保护一个子widget变成了多个子widget。
他的RenderObject基本上都是ContainerRenderObjectMixin和RenderBox的子类,内部保护了头尾两个子节点,并运用存储在parentData中的双相链表保护一切的子RenderObject。
四、谈谈ProxyWidget
最终稍微提一下ProxyWidget。ProxyElement也上ComponentElement的子类。和StatefulWidget 以及StatelessWidget是兄弟联系。也有后代维系的才能,只不过他的build办法是固定的,回来的便是child。

1. InheritedWidget
咱们获取 Theme,MediaQuery数据的时分,都是运用了InheritedWidget
MediaQuery.of(context).size.width;
Theme.of(context).appBarTheme;
经过context 也便是Element实例,获取先人节点的数据。完结数据共享的效果。
Element中保护了先人的一切InheritedElement映射,就可以在需求的时分直接经过后代Element获取。
2. ParentDataWidget
ParentDataWidget供给了子组件向父组件传递烘托信息的才能。
Flexible,Positioned 等组件都是ParentDataWidget 的子类。
需求留意的是:ParentDataWidget只用于烘托信息的传递
在Element.attachRenderObject的时分会调用updateParentData,然后会曲折调用到对应的ParentDataWidget.applyParentData。可以看出只要子组件是RenderObjectWidget子类的时分才会使用对应的ParentDataWidget传递信息。
由此可知,只要在子节点烘托的时分,才会使用RenderObject的数据传递赋值。

子节点的ParentData目标由父布局创立代码如下,创立机遇在子节点刺进的时分履行。

最终
作为开发者,很多时分完结一个使命只会建立在运用的层面。关于为什么这么运用往往不甚了解。
假如咱们能更多的学习他的原理。那么假如在开发中碰到问题,咱们可以愈加得心应手得去解决。
flutter布局烘托的原理以前总是一层雾蒙在我地眼前。但现在,终于有一片薄雾散去,内部轮廓在我面前变得明晰。
坚持学习,才智真实的世界。
小试
咱们最终测验一下一个简略地布局,剖析其三棵树结构。嵌套结构如下。其间builder是StatelessWidget。Column是 MultiChildRenderObjectWidget其他都是SingleChildRenderObjectWidget。
void main() {
runApp(Builder(builder: (context) {
return Column(
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Center(
child: SizedBox(
width: 100,
height: 100,
child: ColoredBox(color: Colors.blue),
),
),
Expanded(
child: ColoredBox(color: Colors.red),
),
],
);
}));
}
展现出来的款式如下。

剖析得出的三棵树如下,源头从RenderView而起,然后构建出RenderObjectToWidgetAdapter,再构建出RootRenderObjectElement。由此从根开端三棵树的循环,直到叶子节点。
RenderObject和Widget并非一一对应,只要RenderObjjectWidget才有,可是RenderObject能主动找出自己的组件RenderObjject 主动刺进到其child中,所以也能主动成树。
至此,咱们的Widget开始了解完结。





















