Flutter之Element更新机制简单分析

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/chunqiuwei/article/details/88364366

本篇博客将要分析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的具体作用是什么?
后面会继续分析

猜你喜欢

转载自blog.csdn.net/chunqiuwei/article/details/88364366