Flutter 笔记 | Flutter 核心原理(二)关键类和启动流程

本文深入探讨了Flutter的核心原理,包括Widget、Element、BuildContext和RenderObject的类结构和作用,阐述了启动流程中根节点的构建、首帧渲染的步骤,以及setState的执行时机和安全更新。此外,文章还详细解析了Flutter的渲染管线,从Frame、SchedulerPhase到渲染流程的七个步骤,展示了如何从用户输入到屏幕显示的全过程。
摘要由CSDN通过智能技术生成

Widget、Element、BuildContext 和 RenderObject

Widget

Widget关键类及其子类继承关系如图所示:

在这里插入图片描述

其中,WidgetWidget Tree所有节点的基类。Widget的子类主要分为3类:

  • 第1类是RenderObjectWidget的子类,具体来说又分为SingleChildRenderObjectWidget(单子节点容器)、LeafRenderObjectWidget(叶子节点)、MultiChildRenderObjectWidget(多子节点容器),它们的共同特点是都对应了一个RenderObject 的子类,可以进行LayoutPaint等逻辑。

  • 第2类是StatelessWidgetStatefulWidget,它们是开发者最常用的Widget,自身不具备绘制能力(即不对应Render Object),但是可以组织和配置RenderObjectWidget类型的Widget

  • 第3类是ProxyWidget,具体来说又分为ParentDataWidgetInheritedWidget,它们的特点是为其子节点提供额外的数据。

Element

Element的关键类及其子类继承关系如图所示:

在这里插入图片描述
从图5-2中可以清楚的看到Element的继承关系,它实现了BuildContext接口,图5-2与图5-1相对应,每一个Element都有一个对应的WidgetElement有两个直接的子类 ComponentElementRenderObjectElement,其中 ComponentElement 的两个子类 StatelessElementStatefulElement 就分别对应了 StatelessWidgetStatefulWidget

我们知道最终的UI树其实是由一个个独立的Element节点构成。组件最终的Layout渲染都是通过RenderObject来完成的,从创建到渲染的大体流程是:根据Widget生成Element,然后创建相应的RenderObject并关联到Element.renderObject属性上,最后再通过RenderObject来完成布局排列和绘制。

Element就是Widget在UI树具体位置的一个实例化对象,大多数Element只有唯一的renderObject,但还有一些Element会有多个子节点,如继承自RenderObjectElement的一些类,比如MultiChildRenderObjectElement。最终所有ElementRenderObject构成一棵树,我们称之为”Render Tree“即”渲染树“。

总结一下,我们可以认为Flutter的UI系统包含三棵树:Widget树Element树渲染树。他们的依赖关系是:Element树根据Widget树生成,而渲染树又依赖于Element树,如图所示。

在这里插入图片描述
现在我们重点看一下ElementElement的生命周期如下:

  1. Framework 调用Widget.createElement 创建一个Element实例,记为element

  2. Framework 调用 element.mount(parentElement,newSlot)mount方法中首先调用element所对应WidgetcreateRenderObject方法创建与element相关联的RenderObject对象,然后调用element.attachRenderObject方法将element.renderObject添加到渲染树中插槽指定的位置(这一步不是必须的,一般发生在Element树结构发生变化时才需要重新添加)。插入到渲染树后的element就处于“active”状态,处于“active”状态后就可以显示在屏幕上了(可以隐藏)。

  3. 当有父Widget的配置数据改变时,同时其State.build返回的Widget结构与之前不同,此时就需要重新构建对应的Element树。为了进行Element复用,在Element重新构建前会先尝试是否可以复用旧树上相同位置的elementelement节点在更新前都会调用其对应WidgetcanUpdate方法,如果返回true,则复用旧Element,旧的Element会使用新Widget配置数据更新,反之则会创建一个新的Element

    Widget.canUpdate主要是判断newWidgetoldWidgetruntimeTypekey是否同时相等,如果同时相等就返回true,否则就会返回false。根据这个原理,当我们需要强制更新一个Widget时,可以通过指定不同的Key来避免复用。

  4. 当有祖先Element决定要移除element 时(如Widget树结构发生了变化,导致element对应的Widget被移除),这时该祖先Element就会调用deactivateChild 方法来移除它,移除后element.renderObject也会被从渲染树中移除,然后Framework会调用element.deactivate 方法,这时element状态变为“inactive”状态。

  5. inactive”态的element将不会再显示到屏幕。为了避免在一次动画执行过程中反复创建、移除某个特定element,“inactive”态的element在当前动画最后一帧结束前都会保留,如果在动画执行结束后它还未能重新变成“active”状态,Framework就会调用其unmount方法将其彻底移除,这时element的状态为defunct,它将永远不会再被插入到树中。

  6. 如果element要重新插入到Element树的其他位置,如elementelement的祖先拥有一个GlobalKey(用于全局复用元素),那么Framework会先将element从现有位置移除,然后再调用其activate方法,并将其renderObject重新attach到渲染树。

在这里插入图片描述

总结:

  • 一个Element对象将在被创建时初始化initial状态,并在通过mount方法加入Element Tree后变为active状态;当该节点对应的Widget失效后,其自身会通过deactivate方法进入inactive状态。如果在当前帧的Build过程中,有其他Element节点通过key复用了该节点,则会通过activate方法使得该节点再次进入active状态;如果当前帧结束后该节点仍不在Element Tree中,则会通过unmount方法进行卸载,并进入defunct状态,等待后续逻辑的销毁

看完Element的生命周期,可能有些人会有疑问,开发者会直接操作Element树吗?

其实对于开发者来说,大多数情况下只需要关注Widget树就行,Flutter框架已经将对Widget树的操作映射到了Element树上,这可以极大的降低复杂度,提高开发效率。

但是了解Element对理解整个Flutter UI框架是至关重要的,Flutter正是通过Element这个纽带将WidgetRenderObject关联起来,了解Element层不仅会帮助开发者对Flutter UI框架有个清晰的认识,而且也会提高自己的抽象能力和设计能力。另外在有些时候,我们必须得直接使用Element对象来完成一些操作,比如获取主题Theme数据。

BuildContext

我们已经知道,StatelessWidgetStatefulWidgetbuild方法都会传一个BuildContext对象:

Widget build(BuildContext context) {
   }

我们也知道,在很多时候我们都需要使用这个context 做一些事,比如:

Theme.of(context) // 获取主题
Navigator.push(context, route) // 入栈新路由
Localizations.of(context, type) // 获取Local
context.size // 获取上下文大小
context.findRenderObject() // 查找当前或最近的一个祖先RenderObject

那么BuildContext到底是什么呢,查看其定义,发现其是一个抽象接口类:

abstract class BuildContext {
   
    ...
}

那这个context对象对应的实现类到底是谁呢?我们顺藤摸瓜,发现build调用是发生在StatelessWidgetStatefulWidget对应的StatelessElementStatefulElementbuild方法中,例如在StatelessElement中:

class StatelessElement extends ComponentElement {
   
  ...
  
  Widget build() => widget.build(this);
  ...
}

同样在StatefulElement 中:

class StatefulElement extends ComponentElement {
   
  ...	
  
  Widget build() => state.build(this);
  ...
}

发现build传递的参数是this,很明显!这个BuildContext就是StatelessElementStatefulElement本身。但StatelessElementStatefulElement本身并没有实现BuildContext接口,继续跟踪代码,发现它们间接继承自Element类,然后查看Element类定义,发现Element类果然实现了BuildContext接口:

abstract class ComponentElement extends Element {
   ...}
abstract class Element extends DiagnosticableTree implements BuildContext {
   ...}

至此真相大白,BuildContext就是widget对应的Element,所以我们可以通过contextStatelessWidgetStatefulWidgetbuild方法中直接访问Element对象。我们获取主题数据的代码Theme.of(context)内部正是调用了ElementdependOnInheritedWidgetOfExactType()方法。

总结:BuildContext 就是 Element本尊,通过 BuildContext 的方法调用就是在操作 ElementWidget 是外衣,而 Element就是外衣下的裸体。

BuildContext 的另一层含义

关于 BuildContext 的另一层含义就是,它是对WidgetWidget树中的位置的引用,它包含了关于WidgetWidget树中的位置的信息,而不是关于Widget本身的信息。

以主题为例,由于每个Widget都有自己的BuildContext ,这意味着如果你将多个主题分散在树中,那么获取一个Widget的主题可能会返回与另一个Widget不同的结果。在计数器应用示例程序中的主题特定情况下,或在其他of方法中,你将会获取到树中距离最近的该类型的父节点

在这里插入图片描述

进阶

我们可以看到Element是Flutter UI框架内部连接widgetRenderObject的纽带,大多数时候开发者只需要关注widget层即可,但是widget层有时候并不能完全屏蔽Element细节,所以Framework在StatelessWidgetStatefulWidget中通过build方法参数又将Element对象也传递给了开发者,这样一来,开发者便可以在需要时直接操作Element对象。

那么现在有两个问题:

1. 如果没有 widget 层,单靠 Element 层是否可以搭建起一个可用的UI框架?如果可以应该是什么样子?
2. Flutter UI 框架能不做成响应式吗?

对于问题 1,答案当然是肯定的,因为我们之前说过widget树只是Element树的映射,它只提供描述UI树的配置信息,Widget 就是外衣,一个人不穿衣服当然也可以比较羞耻地活着,但是穿上衣服他会活的更体面,即便不依赖Widget 我们也可以完全通过Element来搭建一个UI框架。

下面举一个例子:

我们通过纯粹的Element来模拟一个StatefulWidget的功能,假设有一个页面,该页面有一个按钮,按钮的文本是一个9位数,点击一次按钮,则对9个数随机排一次序,代码如下:

class HomeView extends ComponentElement{
   
  HomeView(Widget widget) : super(widget);
  String text = "123456789";

  
  Widget build() {
   
    Color primary = Theme.of(this).primaryColor; //1
    return GestureDetector
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

川峰

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值