携手创造,一起成长!这是我参加「日新方案 8 月更文挑战」的第2天,点击检查活动概况
Flutter的众多Widget傍边,有效果于烘托的RenderObjectWidget、聚集于功用整合的StatefulWidget。可是,还有一个大类,ProxyWidget也相同值得咱们重视。
与其相关的有两个大类:
- InhertedWidget
- ParentDataWidget(代表:Positioned、Expanded)
这两个Widget,无非都是数据的向下传递,其一InheritedWidget更多的是事务数据,比如用户的ID、购物车的条目等等,而ParentDataWidget一般都是视图的数据,Stack需求运用parentData
参数中的长宽
、偏移量
来完结对子Widget的定位。
所以,咱们能够依据ProxyWidget的子类,向上预先给ProxyWidget扣一个数据同享的「帽子」。
1. ProxyWidget和ProxyElement的主要功用
ProxyWidget自身是抽象的,需求咱们重写它的createElement()办法:
class CustomProxyWidget extends ProxyWidget {
const CustomProxyWidget({required Widget child}) : super(child: child);
@override
Element createElement() => CustomProxyElement(this);
}
而ProxyElement则要重写notifyClients办法。
class CustomProxyElement extends ProxyElement {
CustomProxyElement(ProxyWidget widget) : super(widget);
@override
void notifyClients(covariant ProxyWidget oldWidget) {
//......
}
}
整个ProxyElement的要害代码,就notifyClients
这个函数的完结,它传入了一个老的、支持协变的ProxyWidget进来,这意味着传进来的应该是一个老的CustomProxyWidget
的实例,这意味着咱们在notifyClients
中,能够一起拿到老的CustomProxyWidget实例
和当时CustomProxyWidget实例
的引用,分别是oldWidget
和this.widget
。
一新一旧,不难看出ProxyWidget的notifyClients
调用,应该是要去做一些新旧Widget的数据比较而存在的。
比如,咱们能够这样重写它:
@override
void notifyClients(covariant ProxyWidget oldWidget) {
if((oldWidget as CustomProxyWidget).data != (widget as CustomProxyWidget).data){
// 告诉所有订阅者,数据变动了
_clients.foreach((e)=>e.notify());
}
}
咱们能够依据data属性(data是CustomProxyWidget新增的一个int类型的字段)的变化,来决议是否需求告诉订阅者的Element是否去从头制作子Widget,一旦data产生了变化,那么就去遍历_clients中的数据,并调用e.notify
操作监听者从头制作视图。
这让咱们不由和InheritedWidget
的updateShouldNotify
联系起来,简略剖析一下updateShouldNotify
的调用链条:
InhertiedElement#update -> updateShouldNotify() 判断是否需求更新数据
InhertiedElement#update -> callsuper 即调用ProxyElement的update办法
ProxyElement#update -> notifyClients();
显然,InheritedWidget将notifyClients做了一个封装updateShouldNotify
,并把这个封装放在Widget层,而不是直接让开发者去重写notifyClients
这一层,这么做的原因其实和BuildContext存在的含义是相同的,让上层应用开发者只重视Widget,而更少地去感知Element的存在。
总而言之,notifyClients
存在的效果和含义,便是告诉订阅它的子Widget,以完结子Widget的更新,咱们也能稍稍瞥见一些ProxyWidget和ProxyElement的效果,大体上都是和数据传输和同享相关的。
2. InheritedWidget
基于观察者形式的InheritedWidget
,它的运用咱们就不做过多的叙述了,全体上而言,就三步走:
- 注册:运用BuildContext注册监听
- 经过BuildContext获取数据
- 告诉:改动促进监听者的数据重绘
这是一个十分典型的观察者形式的运用进程,只不过InheritedWidget为咱们做了一些封装,「注册」、「告诉」操作变得愈加地“荫蔽”了。
2.1 注册
运用InheritedWidget
时,咱们并没有手动地调用addListener、addObserver这类的办法,去主动添加监听,这一切都是无感的。咱们一般经过如下办法获取到InheritedWidget中的数据。
context.dependOnInheritedWidgetOfExactType<ShareDataWidget>();
这一行代码已经包括两个进程了:注册监听和获取数据。
在InheritedElement
傍边,有一个特别的结构,它存储了咱们上面经过context调用时的context,这样来完结注册的监听,并且,在注册完结之后,会将所需求的数据回来给调用者,这样一来,监听注册、数据的获取这一个操作就合二为一了。
final Map<Element, Object?> _dependents = HashMap<Element, Object?>();
2.2 告诉
关于StatefulWidget的重绘,咱们一定会想到一个办法:markNeedsBuild()
,所以,咱们就顺着上述的调用,查找是否有相关的调用,咱们能够看看归于InheritedElement的notifyClients
的调用链:
1. InheritedElement# notifyClients
2. InheritedElement# notifyDependent(oldWidget, dependent);
3. dependent#didChangeDependencies();
一路从notifyClients
调用到_dependents
中的某个dependent
的didChangeDependencies
办法,这便是告诉的整个流程,InheritedWidget经过这样的调用,告诉所有挂载着的监听者,即其他需求InheritedWidget数据的Widget的BuildContext,并调用BuildContext的didChangeDependencies
,它的完结如下:
@mustCallSuper
void didChangeDependencies() {
……
markNeedsBuild();
}
至此,InheritedWidget是怎么告诉到子Widget进行更新的整个链路已经是十分清晰了。
由于didChangedDepenedencies()的存在,只要添加了依赖的结点才会由于数据的更新而造成节点的rebuild,而不会像StatefulWidget相同,对整棵子树做一次完全的rebuild,这是整个ProxyWidget/ProxyElement的特性。
2.3 何时更新?
InheritedWidget自身只负责数据的向下传递,子Widget能够从InheritedWidget中读出数据,可是,比如咱们的子Widget中的onPressed的回调函数中,对InheritedWidget中的数据进行修正,通常状况下是无法完结UI的更新的,由于InheritedWidget调用notifyClients()
是有机遇约束的。
仅当是ProxyElement#update()
被调用时,才会调用updateShouldNotify()
去评估是否要调用notifyClients
去更新布局。而一般都数据修正,例如int++
、String赋值
等等并不能触发notifyClients
调用。
所以,只要Element#update()
办法调用时,才干驱动子Widget产生视图更新,而Element#update()
办法仅在:Element不变,Widget产生改动的时分才会触发,常见于Widget作为一个装备,产生了改动,而Element产生了复用的状况。比如State调用build办法构建了一个新的Widget子树,这个子树中的Widget都是全新的Widget,并且假如只是修正Text对应的String中的内容,Text对应的Element此刻就会产生复用,这个进程便是Element的update()
,即 用新的newWidget替换掉旧的oldWidget的进程,能够了解为Element的装备的改动。
所以,InheritedWidget的更新就有必要依赖于InheritedWidget的上层更新,比如去调用setState等等,这个触发条件好像有一点苛刻了,咱们肯定是期望在子Widget中修正了InheritedWidget中的数据之后,就直接就能反应到视图。
咱们能够在onPressed等回调办法
中,调用完修正办法之后,手动调用一下setState来手动重建Widget,也能够在InheritedWidget中自己界说一个相关的办法,传入Context,统一处理。
3. ParentDataWidget
之前介绍InheritedWidget主要是讲了它作为ProxyWidget,它的notifyClients
是怎么完结的,作为ProxyWidget的另一个分支,ParentDataWidget也是一个十分常用的Widget,它的常见完结类包括:Flexible(常用Expanded)
、Positioned
等等。它们都有一个十分明显的特点:具有一个其父组件(Flext、Stack)需求的一个额定信息,父组件会运用这个额定的信息对当时组件进行布局、定位。
相比较于InheritedWidget,ParentDataWidget的运用场景更多的是偏向于视图自身的数据,比如尺度、偏移量等等。
3.1 Positioned
首要咱们来看看Positioned,Stack嵌套Positioned,在Positioned能够设置height/width和left/top/right/bottom等一系列的尺度、位置属性,咱们需求重视的,是ParentDataElement对应的的notifyClients终究干了些什么。
咱们先来看看Positioned的功用。Positioned先将传递进来的renderObject目标
中的parentData结构取出,然后再向其中塞数据,之后的布局进程中,Stack就能够依据StackParentData
中的数据进行布局了。
ParentDataElement的notifyClients办法,只调用了一个办法,咱们能够快速地定位到_applyParentData办法
:
@override
void applyParentData(RenderObject renderObject) {
assert(renderObject.parentData is StackParentData);
final StackParentData parentData = renderObject.parentData! as StackParentData;
……
}
这里传进来的正是Positioned
的child属性对应的RenderObject
,Positioned将设置的尺度、偏移量作为一个StackParentData传递进去,然后再Render阶段对其进行位置的确认和布局。
接下来的场景如下:Stack下面套了三个Positioned,对应三个具有色彩的Container。
Positioned自身是不参加Render的,咱们能够很清楚地看到,RenderStack的child直接便是RenderColoredBox,即一个具有色彩的Box,是由Container创立的,而不是一个Positioned
(Container自身是一个复合型的StatelessWidget)。咱们能够模糊地了解成,RenderTree下,Stack下直接便是Container。
ProxyWidget还是会存在于Element、Widget树傍边的,只是在烘托的时分,它并不是一个RenderObject节点,所以,自然而然不参加烘托,可是它的数据还存在它的孩子对应的ParentData傍边。从头构建时,也是调用renderObject.parent(在RenderTree上的parent,即Stack)进行重建
所以,ProxyWidget自身是不参加烘托的,他只作为一个中心Widget,为下层的Child对应的RenderObject,提供上层(Stack)所需求的数据(尺度、偏移量等等)。
同为ParentDataWidget的Flexible同理,只不过把适用于Stack的StackParentData,换成了适用于Flex的FlexParentData,以StackParentData为例,咱们只需求知道它的数据是记录在Postioned的child对应的RenderObject下,交给的父布局Stack运用即可,ParentDataWidget的任务也仅限于此。
4. 后记
已然Positioned对应的Element也是ProxyElement的子类,那么它的notifyClients的调用就和InheritedWidget相同,当Element#update调用时,才会调用notifyClients,去从头为子Widget设置StackParentData(尺度、宽高数据),然后去从头布局子Widget。
这也是ProxyElement一贯的处理方式,当ProxyWidget对应的数据产生改动(InheritedWidget一般是事务数据,ParentDataWidget一般是一些视图数据),才会去重建视图,而Widget数据产生改动的仅有办法,便是从头创立一个Widget,而不是在原有的Widget上经过回调等手法来进行赋值、增减等等,这种状况并不视为Widget的改动。
从Element的角度来说,假如Widget想要改动就必然要经过Element#update办法,即使是StatefulWidget,它的改动也是从State调用setState开端,然后StatefulWidget去rebuild一个新的Child Widget子树,再调用Element的update办法,将新的子树挂载上来完结新旧数据的更迭。
简略来说,默许状况下,数据的变更有必要准确到Widget层面,Element才有或许看得见。
一旦以为数据产生了改动,那么ProxyElement则会经过notifyClients办法,告诉所有的监听者,监听者此刻的行为:
- 假如是InheritedWidget,那么便是调用监听者的didChangeDependencies,重建监听者对应的视图。
- 假如是ParentDataWidget,那么便是调用ParentDataElement的applyParentData函数,去从头build它的子集。
~end