目录 概述:Widget的本质:分类:WidgetStatelessWidgetStatefulWidgetStateParentDataWidgetRenderObjec
所有的一切都可以被称为widget
在开发 Flutter 应用过程中,接触最多的无疑就是Widget
,是『描述』 Flutter UI 的基本单元,通过Widget
可以做到:
Widget
嵌套);font
、color
等);padding
、center
等);Google 在设计Widget
时,还赋予它一些鲜明的特点:
Widget
都是不可变的(immutable),即其内部成员都是不可变的(final
),对于变化的部分需要通过「Stateful Widget-State」的方式实现;Widget
设计遵循组合大于继承这一优秀的设计理念,通过将多个功能相对单一的Widget
组合起来便可得到功能相对复杂的Widget
。在Widget源码中有这样一段注释:
这段注释阐明了Widget
的本质:用于配置Element
的,Widget
本质上是 UI 的配置信息 (附带部分业务逻辑)。
我们通常会将通过
Widget
描述的 UI 层级结构称之为「Widget Tree」,但与「Element Tree」、「RenderObject Tree」以及「Layer Tree」相比,实质上并不存在「Widget Tree」。为了描述方便,将 Widget 组合描述的 UI 层级结构称之为「Widget Tree」,也未尝不可。
Widget
,所有 Widget 的基类。
如上图所示,在 Widget
基类中有 3 个重要的方法 (属性):
GlobalKey 是一类较特殊的 key,在介绍 Element 时会附带介绍。
Widget
都有一个与之对应的Element
,由该方法负责创建,createElement
可以理解为设计模式中的工厂方法,具体的Element
类型由对应的Widget
子类负责创建;Widget
类的默认实现为:2个Widget
的runtimeType
与key
都相等时,返回true
,即可以直接更新 (key 为 null 时,认为相等)。上述更新流程,同样在介绍 Element 时会重点分析。
无状态-组合型 Widget,由其build
方法描述组合 UI 的层级结构。在其生命周期内状态不可变。
/// A widget that does not require mutable state.
///
/// A stateless widget is a widget that describes part of the user interface by
/// building a constellation of other widgets that describe the user interface
/// more concretely. The building process continues recursively until the
/// description of the user interface is fully concrete (e.g., consists
/// entirely of [RenderObjectWidget]s, which describe concrete [RenderObject]s).
具体是两个方法:
StatelessElement
,一般情况下StatelessWidget
子类不必重写该方法,即子类对应的 Element 也是StatelessElement
;
@override
StatelessElement createElement() => StatelessElement(this);
@protected
Widget build(BuildContext context);
以『声明式 UI』的形式描述了该组合式 Widget 的 UI 层级结构及样式信息,也是开发 Flutter 应用的主要工作『场所』。该方法在 3 种情况下被调用:
当「Parent Widget」或 依赖的「Inherited Widget」频繁变化时,build
方法也会频繁被调用。因此,提升build
方法的性能就显得十分重要,Flutter 官方给出了几点建议:
const
Widget,*为 Widget 提供const
构造方法;关于 const constructor 可以看看我这篇文章。
GlobalKey
;有状态-组合型 Widget,但要注意的是StatefulWidget
本身还是不可变的,其可变状态存在于State
中。
/// A widget that has mutable state.
///
/// State is infORMation that (1) can be read synchronously when the widget is
/// built and (2) might change during the lifetime of the widget. It is the
/// responsibility of the widget implementer to ensure that the [State] is
/// promptly notified when such state changes, using [State.setState].
具体有两个方法:
StatefulElement
,一般情况下StatefulWidget
子类不用重写该方法,即子类对应的Element 也是StatefulElement
;
@override
StatefulElement createElement() => StatefulElement(this);
StatefulElement
的构造方法中被调用。可以简单地理解为当「Stateful Widget」被添加到 Widget Tree 时会调用该方法。
@protected
@factory
State createState(); // ignore: no_logic_in_create_state, this is the original sin
StatefulElement(StatefulWidget widget)
: _state = widget.createState(),
super(widget) {
_state._element = this;
_state._widget = widget;
}
实际上是「Stateful Widget」对应的「Stateful Element」被添加到 Element Tree 时,伴随「Stateful Element」的初始化,createState
方法被调用。从后文可知一个 Widget 实例可以对应多个 Element 实例 (也就是同一份配置信息 (Widget) 可以在 Element Tree 上不同位置配置多个 Element 节点),因此,createState
方法在「Stateful Widget」生命周期内可能会被调用多次。
另外,需要注意的是配有GlobalKey
的 Widget 对应的 Element 在整个 Element Tree 中只有一个实例。
有状态小部件 的逻辑和Stateful Widget
State 用于处理「Stateful Widget」的业务逻辑以及可变状态
由于其内部状态是可变的,故 State 有较复杂的生命周期:
如上图,State 的生命周期大致可以分为 8 个阶段:
StatefulElement.constructor
–> StatefulWidget.createState
创建 State 实例;从
StatefulElement.constructor
中的_state._element = this;
可知,State._emelent
指向了对应的 Element 实例,而我们熟知的State.context
引用的就是这个_element
:BuildContext get context => _element;
。
State
实例与Element
实例间的绑定关系一经确定,在整个生命周期内不会再变了 (Element 对应的 Widget 可能会变,但对应的 State 永远不会变),期间,Element
可以在树上移动,但上述关系不会变 (即「Stateful Element」是带着状态移动的)。
State.initState
,子类可以重写该方法执行相关的初始化操作 (此时可以引用context
、widget
属性);State.didChangeDependencies
,该方法在 State 依赖的对象 (如:「Inherited Widget」) 状态发生变化时也会被调用,*子类很少需要重写该方法,*除非有非常耗时不宜在build
中进行的操作,因为在依赖有变化时build
方法也会被调用;build
方法此后可能会被多次调用,在状态变化时 State 可通过setState
方法来触发其子树的重建;State.didUpdateWidget
方法被调用,子类重写该方法去响应 Widget 的变化;上述 3 棵树以及更新流程在后续文章中会有详细介绍
State.deactivate
方法,由于被移除的节点可能会被重新插入树中某个新的位置上,故子类重写该方法以清理与节点位置相关的信息 (如:该 State 对其他 element 的引用)、同时,不应在该方法中做资源清理;重新插入操作必须在当前帧动画结束之前
State.build
方法被再次调用;State.dispose
方法被执行,State 生命周期随之结束,此后再调用State.setState
方法将报错。子类重写该方法以释放任何占用的资源。至此,State 中的核心方法基本都已在上述过程中介绍了,下面重点看一下setState
方法:
void setState(VoidCallback fn) {
assert(fn != null);
assert(() {
if (_debugLifecycleState == _StateLifecycle.defunct) {
throw FlutterError.fromParts(<Diagnosticsnode>[...]);
}
if (_debugLifecycleState == _StateLifecycle.created && !mounted) {
throw FlutterError.fromParts(<DiagnosticsNode>[...]);
}
return true;
}());
final dynamic result = fn() as dynamic;
assert(() {
if (result is Future) {
throw FlutterError.fromParts(<DiagnosticsNode>[...]);
}
return true;
}());
_element.markNeedsBuild();
}
setState
方法有几点值得关注:State.dispose
后不能调用setState
(第 4 行);setState
(第 7 行);setState
方法的回调函数 (fn
) 不能是异步的 (返回值为Future
),原因很简单,因为从流程设计上 framework 需要根据回调函数产生的新状态去刷新 UI (第 14 行);setState
方法之所以能更新 UI,是在其内部调用_element.markNeedsBuild()
实现的 (具体过程在介绍 Element 时再详细分析)。关于 State 最后再强调 2 点:
等 3 方法间有正确的订阅 (subscribe) 与取消订阅 (unsubscribe) 的操作:
在initState
中执行 subscribe;
如果关联的「Stateful Widget」与订阅有关,在didUpdateWidget
中先取消旧的订阅,再执行新的订阅;
在dispose
中执行 unsubscribe。
State.initState
方法中不能调用BuildContext.dependOnInheritedWidgetOfExactType
,但State.didChangeDependencies
会随之执行,在该方法中可以调用。ParentDataWidget
以及下面要介绍的InheritedElement
都继承自ProxyWidget
,由于ProxyWidget
作为抽象基类本身没有任何功能,故下面直接介绍ParentDataWidget
、InheritedElement
。
/// Base class for widgets that hook [ParentData] information to children of/// [RenderObjectWidget]s.
ParentDataWidget
作为 Proxy 型 Widget,其功能主要是为其他 Widget 提供ParentData
信息。虽然其 child widget 不一定是 RenderObejctWidget 类型,但其提供的ParentData
信息最终都会落地到 RenderObejctWidget 类型子孙 Widget 上。
ParentData 是『parent renderobject』在 layout『child renderobject』时使用的辅助定位信息,详细信息会在介绍 RenderObject 时介绍。
void attachRenderObject(dynamic newSlot) {
assert(_ancestorRenderObjectElement == null);
_slot = newSlot;
_ancestorRenderObjectElement = _findAncestorRenderObjectElement();
_ancestorRenderObjectElement?.insertChildRenderObject(renderObject, newSlot);
final ParentDataElement<RenderObjectWidget> parentDataElement = _findAncestorParentDataElement();
if (parentDataElement != null)
_updateParentData(parentDataElement.widget);
}
ParentDataElement<RenderObjectWidget> _findAncestorParentDataElement() {
Element ancestor = _parent;
while (ancestor != null && ancestor is! RenderObjectElement)
{
if (ancestor is ParentDataElement<RenderObjectWidget>)
return ancestor;
ancestor = ancestor._parent;
}
return null;
}
void _updateParentData(ParentDataWidget<RenderObjectWidget> parentData)
{
parentData.applyParentData(renderObject);
}
上面这段代码来自RenderObjectElement
,可以看到在其attachRenderObject
方法第 6 行从祖先节点找ParentDataElement
,如果找到就用其 Widget(ParentDataWidget) 中的 parentData 信息去设置 Render Obejct。在查找过程中如查到RenderObjectElement
(第 13 行),说明当前 RenderObject 没有 Parent Data 信息。
最终会调用到ParentDataWidget.applyParentData(RenderObject renderObject)
,子类需要重写该方法,以便设置对应RenderObject.parentData
。
来看个例子,通常配合Stack
使用的Positioned
(继承自ParentDataWidget):
void applyParentData(RenderObject renderObject) {
assert(renderObject.parentData is StackParentData);
final StackParentData parentData = renderObject.parentData;
bool needsLayout = false;
if (parentData.left != left)
{
parentData.left = left;
needsLayout = true;
}
... if (parentData.width != width)
{
parentData.width = width;
needsLayout = true;
}
... if (needsLayout) {
final AbstractNode targetParent = renderObject.parent;
if (targetParent is RenderObject)
targetParent.markNeedsLayout();
}
}
可以看到,Positioned
在必要时将自己的属性赋值给了对应的RenderObject.parentData
(此处是StackParentData
),并对「parent render object」调用markNeedsLayout
(第 19 行),以便重新 layout,毕竟修改了布局相关的信息。
abstract class ParentDataWidget<T extends RenderObjectWidget> extends ProxyWidget
如上所示,ParentDataWidget
在定义上使用了泛型<T extends RenderObjectWidget>
,其背后的含义是:
从当前ParentDataWidget
节点向上追溯形成的祖先节点链(『parent widget chain』)上,在 2 个ParentDataWidget
类型的节点形成的链上至少要有一个『RenderObject Widget』类型的节点。因为一个『RenderObject Widget』不能接受来自 2 个及以上『ParentData Widget』的信息。
/// Base class for widgets that efficiently propagate information down the tree.
// To obtain the nearest instance of a particular type of inherited widget from
///a build context, use [BuildContext.dependOnInheritedWidgetOfExactType].
InheritedWidget 用于在树上向下传递数据。
通过BuildContext.dependOnInheritedWidgetOfExactType
可以获取最近的「Inherited Widget」,需要注意的是通过这种方式获取「Inherited Widget」时,当「Inherited Widget」状态有变化时,会导致该引用方 rebuild。
具体原理在介绍 Element 时会详细分析。
通常,为了使用方便会「Inherited Widget」会提供静态方法of
,在该方法中调用BuildContext.dependOnInheritedWidgetOfExactType
。of
方法可以直接返回「Inherited Widget」,也可以是具体的数据。
有时,「Inherited Widget」是作为另一个类的实现细节而存在的,其本身是私有的(外部不可见),此时of
方法就会放到对外公开的类上。最典型的例子就是Theme
,其本身是StatelessWidget
类型,但其内部创建了一个「Inherited Widget」:_InheritedTheme
,of
方法就定义在上Theme
上:
static MediaQueryData of(BuildContext context, {
bool nullOk = false })
{
final MediaQuery query = context.dependOnInheritedWidgetOfExactType<MediaQuery>();
if (query != null)
return query.data;
if (nullOk) return null;
}
该of
方法返回的是ThemeData
类型的具体数据,并在其内部首先调用了BuildContext.dependOnInheritedWidgetOfExactType
。
我们经常使用的「Inherited Widget」莫过于MediaQuery
,同样提供了of
方法:
InheritedElement
,一般情况下InheritedElement
子类不用重写该方法;如下是MediaQuery.updateShouldNotify
的实现,在新老Widget.data
不相等时才 rebuilt 那依赖的 Widget。
bool updateShouldNotify(MediaQuery oldWidget) => data != oldWidget.data;
真正与渲染相关的 Widget,属于最核心的类型,一切其他类型的 Widget 要渲染到屏幕上,最终都要回归到该类型的 Widget 上。
RenderObjectElement
,由于RenderObjectElement
也是抽象类,故子类需要重写该方法;Element.mount
),即在 Element 挂载过程中同步构建了「Render Tree」(详细过程后续文章会详细分析);
@overrideRenderFlex createRenderObject(BuildContext context) {
return RenderFlex(
direction: direction,
mainAxisAlignment: mainAxisAlignment,
mainAxisSize: mainAxisSize,
crossAxisAlignment: crossAxisAlignment,
textDirection: getEffectiveTextDirection(context),
verticalDirection: verticalDirection,
textBaseline: textBaseline,
)
;
}
上面是Flex.createRenderObject
的源码,真实感受一下 (还是代码更有感觉)。可以看到,用Flex
的信息(配置)初始化了RenderFlex
。
Flex
是Row
、Column
的基类,RenderFlex
继承自RenderBox
,后者继续自RenderObject
。
@overridevoid updateRenderObject(BuildContext context, covariant RenderFlex renderObject)
{
renderObject
..
direction = direction
..mainAxisAlignment = mainAxisAlignment
..
mainAxisSize = mainAxisSize
..
crossAxisAlignment = crossAxisAlignment
..
textDirection = getEffectiveTextDirection(context)
..
verticalDirection = verticalDirection
..
textBaseline = textBaseline;
}
Flex.updateRenderObject
的源码也很简单,与Flex.createRenderObject
几乎一一对应,用当前Flex
的信息修改renderObject
。
RenderObjectWidget
的几个子类:LeafRenderObjectWidget
、SingleChildRenderObjectWidget
、MultiChildRenderObjectWidget
只是重写了createElement
方法以便返回各自对应的具体的 Element 类实例。
至此,重要的基础型 Widget 基本介绍完了,总结一下:
createElement
,本质上是一个工厂方法);createRenderObject
)。到此这篇关于详解Flutter Widget的文章就介绍到这了,更多相关Flutter Widget内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!
--结束END--
本文标题: 详解Flutter Widget
本文链接: https://www.lsjlt.com/news/133808.html(转载时请注明来源链接)
有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341
下载Word文档到电脑,方便收藏和打印~
2024-01-21
2023-10-28
2023-10-28
2023-10-27
2023-10-27
2023-10-27
2023-10-27
回答
回答
回答
回答
回答
回答
回答
回答
回答
回答
0