刚刚看完张风捷特烈的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
开始了解完结。