Flutter Widget源码解析及实战

2021-08-18 15:02:15 浏览数 (1)

阅读本文大约需要5.4分钟。

这是一篇投稿文章,近日,国内外都掀起了Flutter的学习热潮。本文作者分享了自己在学习Flutter Widget时的心得与体会。

Widget

在flutter中所有页面展示出来的元素都是由一个个的widget组成,与原生android开发不同的地方在于flutter中widget不仅仅表示UI元素,他也可以是一个完全和UI无关如GestureDetector,GestureDetector继承自StatelessWidget。

Widget的功能类似于原生android开发中的style文件,用来描述UI的样式,最终真正绘制在屏幕上的是Element。

代码语言:javascript复制

StatelessWidget

无状态的widget一般用于一些静态UI的绘制(例如:Text)或者提供与UI无关的功能(例如:GestureDetector用来管理手势相关的功能),源码如下:

代码语言:javascript复制

StatelessWidget用于不需要维护状态的场景,它通常在build方法中通过嵌套其它Widget来构建UI,在构建过程中会递归的构建其嵌套的Widget,具体如下:

代码语言:javascript复制

StatefulWidget

可变状态的小部件

代码语言:javascript复制

与StatelessWidget不同的是StatefulWidget类中添加了一个新的接口createState(),一个StatefulWidget类会对应一个State类,State表示与其对应的StatefulWidget要维护的状态。下面是StatefulWidget的最佳实践:

  • 尽量将需要该表状态的widget防止在子节点,这样在改变整个渲染树的时候就只需要更新一个widget即可,如果将其防止在父节点那么将会导致当前节点的整个子节点的widget都会被重新渲染。
  • 尽量减少build方法中返回的widget的嵌套层级,理想情况下一个StatefulWidget仅仅只包含一个类型为RenderObjectWidget的子widget。例如:RichText,但显然这是不切实际的,但一个小部件越是接近这个理想,效率越高。
  • 如果子树没有更改,请缓存表示该子树的窗口小部件,并在每次使用时重新使用它。对于要重新使用的窗口小部件,要比创建新的(但配置相同的)窗口小部件更有效。将有状态部分分解为带有子参数的小部件是执行此操作的常用方法。
  • 尽可能使用`const`小部件。(这相当于缓存窗口小部件并重新使用它。)
  • 避免更改任何创建的子树的深度或更改子树中任何窗口小部件的类型。例如,不是返回包含在[IgnorePointer]中的子项或子项,而是始终将子窗口小部件包装在[IgnorePointer]中并控制[IgnorePointer.ignoring]属性。这是因为更改子树的深度需要重建,布局和绘制整个子树,而只更改属性将需要对渲染树进行尽可能少的更改(例如,在[IgnorePointer]的情况下,没有布局)或重绘是必要的)。
  • 如果由于某种原因必须更改深度,请考虑将子树的公共部分包装在具有[GlobalKey]的小部件中,该[GlobalKey]在有状态小部件的生命周期内保持一致。(如果没有其他小部件可以方便地分配密钥,[KeyedSubtree]小部件可能对此有用。)

下面是一个名为`YellowBird`的有状态小部件子类的框架。在这个例子中[State]没有实际状态。State通常表示为私人成员字段。此外,通常小部件有更多的构造函数参数,每个参数都应该为`final`类型。

代码语言:javascript复制

下面的例子显示了更通用的小部件`Bird`,它可以被赋予一种颜色和一个子widget,并且它有一些内部状态,可以调用一个方法来改变它。

代码语言:javascript复制

按照惯例,窗口小部件构造函数仅使用命名参数。可以使用[@required]将命名参数标记为必需。按照惯例,第一个参数是[key],最后一个参数是`child`,`children`。

StatefulWidget生命周期

State中有两个常用属性

  • widget :表示与State实例相关联的widget实例
  • BuildContext:构建widget的上下文
  • initState:当Widget第一次插入到Widget树时会被调用,对于每一个State对象。framework将在创建的每个[State]对象调用此方法一次。重写此方法以执行初始化,该初始化取决于此对象插入树中的位置(即[context])或用于配置此对象的窗口小部件(即[widget])。如果[State]的[build]方法依赖于一个本身可以改变状态的对象,例如[ChangeNotifier]或[Stream],或者一个可以订阅接收通知的其他对象,那么一定要订阅并在[initState],[didUpdateWidget]和[dispose]中取消订阅:您不能使用此方法中的[BuildContext.inheritFromWidgetOfExactType]。但是,[didChangeDependencies]将在此方法之后立即调用,可以在那里使用[BuildContext.inheritFromWidgetOfExactType]。
  • didChangeDependencies:当State对象的依赖发生变化时会被调用,如果父Widget重建并请求树中的此位置更新以显示具有相同[runtimeType]和[Widget.key]的新Widget,则框架将更新此[State]对象的[widget]属性以引用新Widget然后使用上一个Widget作为参数调用此方法。覆写此方法可以在[widget]更改时进行响应(例如,开始隐式动画)。在调用[didUpdateWidget]之后,框架总是调用[build],这意味着对[didUpdateWidget]中的[setState]的任何调用都是多余的。
  • build:它主要是用于构建Widget子树
  • reassemble:此回调是专门为了开发调试而提供的,在热重载(hot reload)时会被调用。
  • didUpdateWidget:在widget重新构建时,framework会调用canUpdate来检测Widget树中同一位置的新旧节点,然后决定是否需要更新。
  • deactivate:当State对象从树中被移除时,会调用此回调。在一些场景下,Flutter framework会将State对象重新插到树中,如包含此State对象的子树在树的一个位置移动到另一个位置时(可以通过GlobalKey来实现)。如果移除后没有重新插入到树中则紧接着会调用dispose()方法。
  • dispose:当State对象从树中被永久移除时调用;通常在此回调中释放资源。

布局类组件相关

布局类组件都会包含一个或多个子组件,不同的布局类组件对子组件排版(layout)方式不同。Element树是通过Widget树来创建的(通过Widget.createElement()),Widget其实就是Element的配置数据。在Flutter中,根据Widget是否需要包含子节点将Widget分为了三类,分别对应三种Element,如下表:

StatelessWidget和StatefulWidget就是两个用于组合Widget的基类,它们本身并不关联最终的渲染对象(RenderObjectWidget)。

最终渲染操作是在build()方法中构建真正的RenderObjectWidget,如Text,它其实是继承自StatelessWidget,然后在build()方法中通过RichText来构建其子树,而RichText才是继承自LeafRenderObjectWidget。

实战

具体效果如下:

相关源码如下:

代码语言:javascript复制

想要明白些道理,遇见些有趣的事 —— 离岛

0 人点赞