本篇博客将要分析Widget的更新机制,在阅读这篇文章之前建议读者阅读Fultter之Element和Widget对应关系解析, 从Element和Widget对应关系这篇博文中可以知道有如下的表关系:
Widget | 说明 | 举例 |
---|---|---|
MultiChildRenderObjectWidget | 该类型的Widget可以添加多个widget | Row,Column,Stack |
SingleChildRenderObjectWidget | 该类型的widget只能添加一个widget | Center,Padding,Container |
LeafRenderObjectWidget | 该类型是树的叶子节点,故不能添加widget | Text,Image,Semantics |
我们知道Widget的解析在mount里面进行的,mout调用了Element的inflateWidget方法开始解析布局:这个观点可以在MultiChildRenderObjectWidget的mount代码得到验证,代码如下:
void mount(Element parent, dynamic newSlot) {
super.mount(parent, newSlot);
_children = List<Element>(widget.children.length);
Element previousChild;
//循环调用各个widget
for (int i = 0; i < _children.length; i += 1) {
//调用element的inflateWidget对widget.children[i]
final Element newChild = inflateWidget(widget.children[i], previousChild);
_children[i] = newChild;
previousChild = newChild;
}
}
当然本文的主题是Flutter的更新机制,为了方便说明本文以SingleChildRenderObjectWidget为参考 来说明所以详细看下SingleChildRenderObjectWidget的源码:
abstract class SingleChildRenderObjectWidget extends RenderObjectWidget {
//有且仅有一个child
final Widget child;
//创建一个SingleChildRenderObjectElement并将this即传入进去
@override
SingleChildRenderObjectElement createElement() => SingleChildRenderObjectElement(this);
}
正如代码所示SingleChildRenderObjectWidget 是一个只包含一个Child Widget的组件。其Element对应的是SingleChildRenderObjectElement,主要代码如下所示:
class SingleChildRenderObjectElement extends RenderObjectElement {
SingleChildRenderObjectElement(SingleChildRenderObjectWidget widget) : super(widget);
//对应子Widget的Element对象。
Element _child;
@override
void mount(Element parent, dynamic newSlot) {
super.mount(parent, newSlot);
//创建子child Widget对应的Element对象,第三个参数传null
_child = updateChild(_child, widget.child, null);
}
}
观察SingleChildRenderObjectWidget和SingleChildRenderObjectElement 可以发现二者都包含了对应的child对象,比如SingleChildRenderObjectWidget拥有了一个child Widget对象。而SingleChildRenderObjectElement 拥有了一个 _child Element对象。SingleChildRenderObjectElement 的mount方法调用了updateChild方法,顾名思义就是更新child。
其实也是做了两件事:
1.如果符合flutter的更新条件,则进行更新操作
2.如果不符合更新,则调用inflateWidget进行UI的重新构建。
让我们来详细看看updateChild方法,该方法在Element对象里。
Element updateChild(Element child, Widget newWidget, dynamic newSlot) {
//如果newWidget是null,且child不是null,那么我们需要从render tree中删除这个widget。
//同时返回一个null
if (newWidget == null) {
if (child != null)
deactivateChild(child);
return null;
}
if (child != null) {
if (child.widget == newWidget) {
//省略处理slot的逻辑
//返回旧的element
return child;
}
//如果满足更新机制
if (Widget.canUpdate(child.widget, newWidget)) {
//省略处理slot的逻辑
//则更新
child.update(newWidget);
//返回一个新的
return child;
}
deactivateChild(child);
}
//不符合更新逻辑,则返回一个新的element对象
return inflateWidget(newWidget, newSlot);
}
下面先来说明下updateChild的前参数:child代表的是old Widget所对应的Element对象。newWidget代表的是最新的widget对象,至于第三个 newSlot可能是一个Element(详见MultiChildRenderObjectElement的mount方法),需要注意的是对于只有一个child的Widget组件,Flutter建议slot传null Subclasses of Element that only have one child should use null for the slot for that child.
,正如SingleChildRenderObjectElement 这样,调用updateChild方法第三个参数直接是null。那么Element的更新规则是什么呢?先总结贴一下官网对该方法返回值的总结:
newWidget == null | newWidget != null | |
---|---|---|
child == null | Returns null. | Returns new [Element]. |
child != null | Old child is removed, returns null. | Old child updated if possible, returns child or new [Element]. |
可以看出有两种情况:newWidget等于null和newWidget !=null
1、newWidget等于null的情况:updateChild方法返回的是一个null,且如果child这个老的element 不为null则删除这个child。
2、newWidget !=null的情况:如果child==null,则返回newWidget 所关联的Element(通过newWidget.createElement方法创建),如果child!=null,则如果满足复用条件,则返回原来的child,否则还是返回新的Element对象,即newWidget创建的Element对象。
child这个element满足复用的条件有两个,第一个是child.widget == newWidget:
if (child.widget == newWidget) {
//返回旧的element
return child;
}
显而易见,如果child原来的widget和newWidget相等,肯定直接复用child这个Element及其widget对象。
第二个条件就是Widget的静态方法canUpdate返回true:
static bool canUpdate(Widget oldWidget, Widget newWidget) {
return oldWidget.runtimeType == newWidget.runtimeType
&& oldWidget.key == newWidget.key;
}
方法也很简单:当且仅当新旧两个widget的runtimeType相等且两个key也相等的时候就采取更新措施:
if (Widget.canUpdate(child.widget, newWidget)) {
//省略处理slot的逻辑
//则更新
child.update(newWidget);
//返回一个新的
return child;
}
另外child.update方法也很简单,就是将新的widget对象赋值给child的widget引用,代码如下:
@mustCallSuper
void update(covariant Widget newWidget) {
_widget = newWidget;
}
到此算是分析完了Widget的更新机制,通过代码来看与其说是更新了widget,不如说是更新了Element。到此为止,本篇博文就此结束,如有不当之处欢迎批评指正,另外以两个问题来结束本篇博文:
1.widget 的 runtimeType是什么?
2.widget的key的具体作用是什么?
后面会继续分析
网友评论