【flutter进阶】Widget源码详解-如何实现自由组合,动态刷新,布局绘制?

刚刚看完张风捷特烈的Flutter 布局探究小册。感觉获益良多。

看到结局的问题:怎么区分StatelessWidgetStatefulWidget 的运用场景,不由开端自问,关于StatefulWidget ,StatelessWidget,以及flutter中Widget的众多子类我真的足够了解吗?

关于自己常常要打交道的东西,假如仅仅一知半解则不利于进步。

下面就从源码的角度来学习下flutter根底的几个Widget 都起到了什么效果。

【flutter进阶】Widget源码详解-如何实现自由组合,动态刷新,布局绘制?

先给个简略总结:

  • 其间StatelessWidget 和 StatefulWidget 起到了组织组合子组件的效果。
  • RenderObjectWidget 起到烘托效果。包括制作偏移和测量信息。
  • ProxyWidget 可以带着信息,以供其他组件运用。

一、探究StatelessWidget的组件构建

在运用StatelessWidget的时分,一般只需求完结一个build办法。就拿咱们常用的Container组件举例,他便是StatelessWidget 的子类。他的build办法回来的便是各种组件的组合嵌套

【flutter进阶】Widget源码详解-如何实现自由组合,动态刷新,布局绘制?

他的各种成员属性也仅仅用来配置子组件的组合办法罢了。

1. StatelessWidget 的build调用机遇,以及widget树遍历流程

Container组件是StatelessWidget的经典子类。

咱们经过断点调试看看Container 组件build办法的调用堆栈

【flutter进阶】Widget源码详解-如何实现自由组合,动态刷新,布局绘制?

ComponentElementperformRebuild 办法调用的时分,触发了build办法,从stateless中获取了build回来的Widget,而又在performRebuild 调用了updateChild办法,对一切的后代Element进行build遍历。

ComponentElement是Widget对应元素StatelessElementStatefulElement的父类。

咱们拉到开端的调用栈。Element栈调用的起点在于attachRootWidget办法。

还记得咱们flutter app开发的起点吗?便是runApp(App())办法,开启了整个flutter app。 attachRootWidget办法正是咱们在调用runApp的时分履行的。

在其间,履行了RenderObjectToWidgetAdapter组件的初始化,将renderViewrootWidget作为入参。并且调用attachToRenderTree回来元素树极点的Element。

【flutter进阶】Widget源码详解-如何实现自由组合,动态刷新,布局绘制?

三颗树的极点

其间renderViewRenderObject树的极点,_renderViewElementElement树的极点。匿名的RenderObjectToWidgetAdapter则是Widget树的极点,可是他没有被引用。Widget树的保护依赖于Element树,rootWidget便是咱们的runApp组件节点,被作为参数挂载到RenderObjectToWidgetAdapter根组件中,被后续的Element挂载循环运用。

Element中也存放了_parent变量,所以咱们经过Element目标可以轻松的追溯到先人节点。

【flutter进阶】Widget源码详解-如何实现自由组合,动态刷新,布局绘制?

咱们从上面的剖析可以得出ComponentElement 的 performRebuild办法是element.build传承要害办法 ,mount办法也能由此挂载出一切子树(其他类型的Element完结方案略有不同)

在ComponentElement中。也由performRebuild构建出一层层的后代节点。代码如下,留意红色方框的代码。

【flutter进阶】Widget源码详解-如何实现自由组合,动态刷新,布局绘制?

第一个红框中是build()办法的履行。意味着每次performRebuild被调用的时分,子组件都会被build出来,由此可知widget是仅有的,每次更新都会有新的Widget生成。

updateChild的过程中,假如子element还未生成,就会调用widget.createElement()办法取得element

咱们再看StatelessWidget 的源码,完结了createElement办法回来了自界说的StatelessElement

【flutter进阶】Widget源码详解-如何实现自由组合,动态刷新,布局绘制?

生成的子Element 都会在ComponentElement中被持有,以便后续更新

【flutter进阶】Widget源码详解-如何实现自由组合,动态刷新,布局绘制?

由此可知,ComponentElement维系了祖孙联系,其子类Element对应的 StatelessWidget,StatefulWidget,ParentDataWidget 和 InheritedWidget都天然拥有后代联系才能。

如下所示,StatefulElementComponentElement 的子类。

【flutter进阶】Widget源码详解-如何实现自由组合,动态刷新,布局绘制?

2. StatelessWidget 和Element在烘托中的更新

widget的创立都是在element树遍历的过程中履行的。 widget树依赖于element树,在Element创立的时分widget实例将会被持有。

StatelessWidget在布局和烘托流程中依赖Element维系,树联系被Element发掘。

【flutter进阶】Widget源码详解-如何实现自由组合,动态刷新,布局绘制?

Element performeRebuild从头构建的时分,有一个是否更新Element的断定机制,以优化功能。

不管是更新update还是挂载mount,每次子widget都会先build()出来。再进行新旧比较。Widget都是一次性的,假如有状况需求保存是由其他办法完结的。 咱们再看updateChild办法。上面一末节说到在子element为空的时分,会在其间createElement。而在子Element不为空的时分,会依据新旧Widget 的不同,进行不同的操作。

【flutter进阶】Widget源码详解-如何实现自由组合,动态刷新,布局绘制?

其间经过新旧widgetequals断定。决定是否复用之前的element。假如复用了element,依据canUpdate办法的回来值,来履行child.update办法。所以咱们可以得出这样一个定论。

widgetcanUpdate 完结,将很大程度上决定 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比较特殊。作为大局的仅有秘钥。供给了对应 widgetBuildContextwidget 的拜访办法。并针对 StatefulWidget。还供给了 State 的拜访。

以便用户对状况进行大局的更新。比方咱们需求在外部运用 BuildContext 进行初始化的时分,可以进行这样调用

【flutter进阶】Widget源码详解-如何实现自由组合,动态刷新,布局绘制?

4. 小结

经过以上对StatelessWidgetComponentElement 的剖析,可以得出以下的判断。

StatelessWidget 根据 ComponentElement。首要功能便是供给了组合各种widget的才能,并维持了祖孙的build传承。

当然在探究傍边也发现了一些技术债款,由于咱们现已知道了statelesswidget的运用场景,关于详细的源码细节先按下不表,在此只记载

  • 生命周期_lifecycleState 起到什么效果
  • _dirty 标记和 markNeedsBuild 的用法和原理是什么
  • BuildOwner 的效果是什么

二、探究StatefulWidget的动态改写机制

StatefulWidgetStateflessWidget 有很多共同之处。最首要的原因便是他们创立的元素都是ComponentElement的子类,其供给了widget后代build传承的才能。

可知StatefulWidgetStateflessWidget相同,也是一个有才能组合各种widget的组件。

1. State生命周期剖析

StatefulWidget 界说了createState办法。供给了状况改写才能。

【flutter进阶】Widget源码详解-如何实现自由组合,动态刷新,布局绘制?

再次从StatefullElementbuild办法入手。直接调用了state.build(this)。代理了state的构建行为。

performRebuild办法中也进行了state.didChangeDependencies生命周期回调。

【flutter进阶】Widget源码详解-如何实现自由组合,动态刷新,布局绘制?

在State中,除了生命周期办法外, 最重要的便是build办法了。效果和StatelessWidget的build办法共同。都是供给了组合widget的才能。 initState则给用户供给了初始化state状况的机会。断点调试看看调用栈怎么。

【flutter进阶】Widget源码详解-如何实现自由组合,动态刷新,布局绘制?

调试中直观看到,在firstBuld的时分,stateinitState被调用。并在之后调用了didChangeDependencies生命周期办法,和build办法。

【flutter进阶】Widget源码详解-如何实现自由组合,动态刷新,布局绘制?

代码中也对办法做了限制,不可以回来Future类型。 所以咱们可以在initState中放心做一些初始化作业,没有异步参加,作业将会在build之前完结。

2. setState办法改写页面办法剖析

关于setState办法。除开生命周期的判断之外,要害代码只要一句,便是调用了element 的markNeedsBuild()

【flutter进阶】Widget源码详解-如何实现自由组合,动态刷新,布局绘制?

该办法将对应的element标记为dirty。并且调用owner``!.scheduleBuildFor(``this``);将其加入到 BuildOwner的脏列表(_dirtyElements)中。 将会在下次帧改写的时分调用BuildOwner.owner.buildScope 从头构建该列表中的元素。

3. 小结

StatelessWidget给运用者供给了一个快捷的布局改写进口,咱们可以运用setState改写布局。该办法会将对应Element标记为待改写元素,在下次帧改写的时分重建布局。状况的改动将会被重建的布局从头获取。

三、探究SingleChildRenderObjectWidget

SingleChildRenderObjectWidget对应的元素类是SingleChildRenderObjectElement。 咱们作为开发者,布局过程中SingleChildRenderObjectWidget 的子类运用频率非常频繁,布局的束缚,偏移和烘托都是由RenderObjectWidget 完结的,SingleChildRenderObjectWidget继承了RenderObjectWidget的烘托才能,并供给了单子传承的才能。布局的过程中该目标的子类不可或缺,flutter框架中也有不少对应的完结类。

Flutter 框架中完结的SingleChildRenderObjectWidget有以下几种。

  1. SizedBox
  2. LimitedBox
  3. ShaderMask
  4. RotatedBox
  5. SizedOverflowBox
  6. Padding

1. 探究SingleChildRenderObjectElement中关于子widget的挂载和更新

SingleChildRenderObjectElement`的`mount` 和 `update`办法都很简略,都是直接调用了`updateChild`办法,传进去的子widget直接是`widget.child

【flutter进阶】Widget源码详解-如何实现自由组合,动态刷新,布局绘制?

这个办法和ComponentElement基本上相同,都是运用canUpdate的成果进行更新或者是创立子Element

1. 以Padding为例了解RenderObjectWidget 的布局和制作完结。

名词解释

RenderObject:烘托目标,flutter目标布局的束缚,制作,位移满是由该目标完结,RenderObject树的祖孙中传递着束缚,以做到布局巨细的传承影响。

RenderObject的创立

RenderObjectWidget 会在mount挂载的时分,创立RenderObject,直接调用widge.createRenderObject。咱们的束缚,制作,位移满是由RenderObject传递和完结的。

【flutter进阶】Widget源码详解-如何实现自由组合,动态刷新,布局绘制?

RenderPadding的布局完结

Padding为例。createRenderObject创立了RenderPadding实例,widget的成员原封不动交给了该实例。

【flutter进阶】Widget源码详解-如何实现自由组合,动态刷新,布局绘制?

束缚(BoxConstraint)是Flutter确认布局巨细的方案,各种RenderObject关于束缚的传递都有自己的完结。

下方是RenderPaddingperformLayout代码。红框标记起来的代码中就展现了Padding的束缚传承逻辑。

其父布局传给自己束缚根底上减去Padding再传递给子RenderObject

调查performLayout办法可以发现,该办法完结了束缚的传递,计算了偏移量Offset,并确认了自己的巨细。

【flutter进阶】Widget源码详解-如何实现自由组合,动态刷新,布局绘制?

确认巨细束缚之后,就会在paint中制作自己和后代。RenderPadding没有自界说制作,直接运用了父类RenderShiftedBox的完结。RenderShiftedBox 供给了offset偏移。在制作子renderObject的时分,为其施加制作偏移量。有些需求计算子布局偏移的widget,如PaddingAlign等,都对RenderShiftedBox进行了完结。

【flutter进阶】Widget源码详解-如何实现自由组合,动态刷新,布局绘制?

可以看到子布局的offset存在他的parentData中。PaddingRender运用的parentDataBoxParentData,内部供给了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的时分进行的初始化。红框中的代码展现了这一细节。

【flutter进阶】Widget源码详解-如何实现自由组合,动态刷新,布局绘制?

到此,就能了解RenderObject是怎么被束缚BoxConstraint,怎么被布局layout,以及怎么被制作paint

1. RenderObjectElement的传承办法

RenderObjectElement 的父子传承在两个子类中完结,在第1小结中现已说到SingleChildRenderObjectWidgetComponentElement非常相似,仅仅直接把widget.child拿来传承,而不再供给build办法以供子组件组合。

MultiChildRenderObjectElement 也相似,只不过作为多子组件,三棵树分叉的首要因子,保护的是children 列表。

【flutter进阶】Widget源码详解-如何实现自由组合,动态刷新,布局绘制?

在mount 和 update 的时分,后代组件会像点了爆竹相同被逐一构建和更新。

1. 小结

每个SingleChildRenderObjectWidget组件都完结了各自的布局和制作方案,也各自处理了束缚并传递下去。

比方ColordBox作为制作组件,借助了RenderColord,制作了自身色彩,束缚则取得是父束缚的最小值。Align作为定位组件,借助了RenderPositionedBox,布局的时分计算了对应的偏移量offset,在制作子布局的时分运用,束缚则在传递的时分转了松束缚。

诸如此类,一切组件都运用了对应的RenderObject满意了各自布局和烘托的一切需求。咱们自己当然也可以自界说对应的RenderObject完结自己的布局。

MultiChildRenderObjectWidgetSingleChildRenderObjectWidget相似,仅仅保护一个子widget变成了多个子widget。

他的RenderObject基本上都是ContainerRenderObjectMixinRenderBox的子类,内部保护了头尾两个子节点,并运用存储在parentData中的双相链表保护一切的子RenderObject

四、谈谈ProxyWidget

最终稍微提一下ProxyWidgetProxyElement也上ComponentElement的子类。和StatefulWidget 以及StatelessWidget是兄弟联系。也有后代维系的才能,只不过他的build办法是固定的,回来的便是child。

【flutter进阶】Widget源码详解-如何实现自由组合,动态刷新,布局绘制?

1. InheritedWidget

咱们获取 Theme,MediaQuery数据的时分,都是运用了InheritedWidget

MediaQuery.of(context).size.width;
Theme.of(context).appBarTheme;

经过context 也便是Element实例,获取先人节点的数据。完结数据共享的效果。

Element中保护了先人的一切InheritedElement映射,就可以在需求的时分直接经过后代Element获取。

2. ParentDataWidget

ParentDataWidget供给了子组件向父组件传递烘托信息的才能。 FlexiblePositioned 等组件都是ParentDataWidget 的子类。

需求留意的是:ParentDataWidget只用于烘托信息的传递

在Element.attachRenderObject的时分会调用updateParentData,然后会曲折调用到对应的ParentDataWidget.applyParentData。可以看出只要子组件是RenderObjectWidget子类的时分才会使用对应的ParentDataWidget传递信息。

【flutter进阶】Widget源码详解-如何实现自由组合,动态刷新,布局绘制?

由此可知,只要在子节点烘托的时分,才会使用RenderObject的数据传递赋值。

【flutter进阶】Widget源码详解-如何实现自由组合,动态刷新,布局绘制?

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

【flutter进阶】Widget源码详解-如何实现自由组合,动态刷新,布局绘制?

【flutter进阶】Widget源码详解-如何实现自由组合,动态刷新,布局绘制?

最终

作为开发者,很多时分完结一个使命只会建立在运用的层面。关于为什么这么运用往往不甚了解。

假如咱们能更多的学习他的原理。那么假如在开发中碰到问题,咱们可以愈加得心应手得去解决。

flutter布局烘托的原理以前总是一层雾蒙在我地眼前。但现在,终于有一片薄雾散去,内部轮廓在我面前变得明晰。

坚持学习,才智真实的世界。

小试

咱们最终测验一下一个简略地布局,剖析其三棵树结构。嵌套结构如下。其间builderStatelessWidgetColumnMultiChildRenderObjectWidget其他都是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),
        ),
      ],
    );
  }));
}

展现出来的款式如下。

【flutter进阶】Widget源码详解-如何实现自由组合,动态刷新,布局绘制?

剖析得出的三棵树如下,源头从RenderView而起,然后构建出RenderObjectToWidgetAdapter,再构建出RootRenderObjectElement。由此从根开端三棵树的循环,直到叶子节点。

RenderObjectWidget并非一一对应,只要RenderObjjectWidget才有,可是RenderObject能主动找出自己的组件RenderObjject 主动刺进到其child中,所以也能主动成树。

【flutter进阶】Widget源码详解-如何实现自由组合,动态刷新,布局绘制?

至此,咱们的Widget开始了解完结。