前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >flutter绘制流程——rebuild

flutter绘制流程——rebuild

作者头像
用户2929716
发布2022-01-18 14:00:03
7320
发布2022-01-18 14:00:03
举报
文章被收录于专栏:流媒体流媒体

rebuild是Element的方法,有两种场景下会被调用:

  1. element第一次构建mount的时候
  2. widget发生变化的时候
代码语言:javascript
复制
void rebuild() {
  if (_lifecycleState != _ElementLifecycle.active || !_dirty)
    return;
  performRebuild();
}

主要逻辑分为2步

  1. 判断状态是否是active,_dirty是否为true
  2. 执行performRebuild(),这是个抽象方法,所以具体rebuild的逻辑由element子类去实现

下面重点来看performRebuild()

performRebuild.png

1 performRebuild()

顾名思义真正执行重新build的地方,因此每个实现类会有所不同,下面看下不同类型Element的实现

1.1 RenderObjectElement

更新renderObject,当然还有一些RenderObjectElement的继承类可能还做了其他逻辑

代码语言:javascript
复制
@override
void performRebuild() {
  widget.updateRenderObject(this, renderObject);
  _dirty = false;
}
1.1.1 SliverMultiBoxAdaptorElement

对于SliverGrid,SliverList,ListView都会用到它,这里逻辑比较多,今天这篇不去细讲,大概了解有关键逻辑 _build(index)和updateChild`,

代码语言:javascript
复制
@override
void performRebuild() {
  super.performRebuild();
  final SplayTreeMap<int, Element?> newChildren = SplayTreeMap<int, Element?>();
  void processElement(int index) {
    //省略...
    final Element? newChild = updateChild(newChildren[index], _build(index), index);
    //省略...
  }
  //省略...
  newChildren.keys.forEach(processElement);
  //省略...
}

1.2 ComponentElement

代码语言:javascript
复制
void performRebuild() {
  Widget? built;
  try {
    //widget重建 如:StatelessElement
    built = build();
  } catch (e, stack) {
  } finally {
    _dirty = false;
  }
  try {
    _child = updateChild(_child, built, slot);
  } catch (e, stack) {
    //..省略
  }
}
  1. 执行build(),作为新的newWidget
  2. _child = updateChild(_child, built, slot);
1.2.1 StatelessElement

未覆写,逻辑同ComponentElement的performRebuild()

1.2.2 StatefulElement
代码语言:javascript
复制
void performRebuild() {
  if (_didChangeDependencies) {
    state.didChangeDependencies();
    _didChangeDependencies = false;
  }
  super.performRebuild();
}

在build之前判断需要didChangeDependencies

总结:performRebuild()实现分两类,ComponentElement和RenderObjectElement

  • RenderObjectElement会updateRenderObject,对于有child的继承类会进行_build(index)和updateChild
  • ComponentElement的performRebuild主要分为两步。1:build(); 2:updateChild。下面依次展开

2 build()

2.1 ComponentElement

代码语言:javascript
复制
/// Subclasses should override this function to actually call the appropriate
/// `build` function (e.g., [StatelessWidget.build] or [State.build]) for
/// their widget.
@protected
Widget build();

用来build返回widget,这个我们就很熟悉了,写UI代码主要围绕在这一块

  1. StatelessElement调用widget.build(this);
  2. StatefulElement 调用state.build(this);
  3. ProxyElement 直接返回widget.child

2.2 SliverMultiBoxAdaptorElement

在1.1.1中的performRebuild()执行的是_build

代码语言:javascript
复制
Widget? _build(int index) {
  return widget.delegate.build(this, index);
}

看到SliverChildDelegate中的定义,和ComponentElement的build意思差不多,只不是事根据传入index来返回widget

代码语言:javascript
复制
/// Returns the child with the given index.
///
/// Should return null if asked to build a widget with a greater
/// index than exists. If this returns null, [estimatedChildCount]
/// must subsequently return a precise non-null value (which is then
/// used to implement [RenderSliverBoxChildManager.childCount]).
///
/// Subclasses typically override this function and wrap their children in
/// [AutomaticKeepAlive], [IndexedSemantics], and [RepaintBoundary] widgets.
///
/// The values returned by this method are cached. To indicate that the
/// widgets have changed, a new delegate must be provided, and the new
/// delegate's [shouldRebuild] method must return true.
Widget? build(BuildContext context, int index);

总结:不管是ComponentElement中的build,还是SliverMultiBoxAdaptorElement中的_build,最终都是用来构建一个Widget。

下面看到performRebuild的下一步updateChild。

3 updateChild

3.1 Element/ComponentElement

Element? updateChild(Element? child, Widget? newWidget, Object? newSlot),用来更新子element

代码语言:javascript
复制
Element? updateChild(Element? child, Widget? newWidget, Object? newSlot) {
  //如果newWidget是null,并且old child非null,直接deactivateChild
  if (newWidget == null) {
    if (child != null)
      deactivateChild(child);
    return null;
  }
  final Element newChild;
  if (child != null) {
    //新旧widget相同的情况
    if (child.widget == newWidget) {
      if (child.slot != newSlot)
        updateSlotForChild(child, newSlot);
      newChild = child;
    } else if (Widget.canUpdate(child.widget, newWidget)) {
      //可以update的情况,也就是runtimetype和key相同
      if (child.slot != newSlot)
        updateSlotForChild(child, newSlot);
      child.update(newWidget);
      newChild = child;
    } else {
      //其他情况,移除旧的,重新inflateWidget新的widget,会创建element
      deactivateChild(child);
      newChild = inflateWidget(newWidget, newSlot);
    }
  } else {
    //old child是null,这里直接inflate新的widget,会创建element
    newChild = inflateWidget(newWidget, newSlot);
  }
  return newChild;
}

总共分以下几种情况

  1. newWidget是null,则清理掉old child(如果old child不为null),返回null
  2. 新旧widget相同,说明数据没有变化,直接返回旧的child
  3. widget可以update,child.update(newWidget); 直接更新old child即可
  4. 其他情况,重新inflateWidget,这里会createElement,清理掉old child(如果old child不为null)

3.2 SliverMultiBoxAdaptorElement

RenderObjectElement的实现类SliverMultiBoxAdaptorElement额外处理的就是更新child的renderobject的parentData

代码语言:javascript
复制
@override
Element? updateChild(Element? child, Widget? newWidget, Object? newSlot) {
  final SliverMultiBoxAdaptorParentData? oldParentData = child?.renderObject?.parentData as SliverMultiBoxAdaptorParentData?;
  final Element? newChild = super.updateChild(child, newWidget, newSlot);
  final SliverMultiBoxAdaptorParentData? newParentData = newChild?.renderObject?.parentData as SliverMultiBoxAdaptorParentData?;

  // Preserve the old layoutOffset if the renderObject was swapped out.
  if (oldParentData != newParentData && oldParentData != null && newParentData != null) {
    newParentData.layoutOffset = oldParentData.layoutOffset;
  }
  return newChild;
}

下面看到update的逻辑

4 update

Element中定义,更新widget

代码语言:javascript
复制
@mustCallSuper
void update(covariant Widget newWidget) {
  _widget = newWidget;
}

4.1 StatelessElement

代码语言:javascript
复制
void update(StatelessWidget newWidget) {
  super.update(newWidget);
  _dirty = true;
  rebuild();
}

4.2 StatefulElement

代码语言:javascript
复制
void update(StatefulWidget newWidget) {
  super.update(newWidget);
  final StatefulWidget oldWidget = state._widget!;
  _dirty = true;
  state._widget = widget as StatefulWidget;
  state.didUpdateWidget(oldWidget) as dynamic;
  rebuild();
}

4.3 RenderObjectElement

代码语言:javascript
复制
void update(covariant RenderObjectWidget newWidget) {
  super.update(newWidget);
  widget.updateRenderObject(this, renderObject);
  _dirty = false;
}

下面看几个典型的实现类

4.3.1 SingleChildRenderObjectElement
代码语言:javascript
复制
@override
void update(SingleChildRenderObjectWidget newWidget) {
  super.update(newWidget);
  assert(widget == newWidget);
  _child = updateChild(_child, widget.child, null);
}
4.3.2 MutliChildRenderObjectElement
代码语言:javascript
复制
@override
void update(MultiChildRenderObjectWidget newWidget) {
  super.update(newWidget);
  _children = updateChildren(_children, widget.children, forgottenChildren: _forgottenChildren);
  _forgottenChildren.clear();
}

总结:对于update方法首先一定会做的就是更新_widget。然后对于ComponentElement和RenderObjectElement的逻辑有所不同。

  • ComponentElement主要会进行rebuild();这样又回到最初的rebuild,只是到了子节点
  • RenderObjectElement则会更新自己的renderObject,然后根据拥有child是否是多个逻辑有所不同,如:
    • SingleChildRenderObjectElement只有一个child,执行updateChild 这样也回到了前面的步骤3
    • MutliChildRenderObjectElement可能有多个child,执行updateChildren

下面重点开看updateChildren

5 updateChildren

RenderObjectElement中定义,针对可能有多个child的element的更新逻辑

5.1 定义新旧children的开始和结束位置,用于后面遍历

代码语言:javascript
复制
//定义old和new的首尾位置
int newChildrenTop = 0;
int oldChildrenTop = 0;
int newChildrenBottom = newWidgets.length - 1;
int oldChildrenBottom = oldChildren.length - 1;
//根据old和new的长度判断,如果相同则newChildren直接使用oldChildren,如果不同,则创建一个长度为newWidgets.length的list,使用_NullElement.instance来填充
final List<Element> newChildren = oldChildren.length == newWidgets.length ?
oldChildren : List<Element>.filled(newWidgets.length, _NullElement.instance, growable: false);

5.2 从开始位置遍历children,主要处理可以直接updateChild的情况,碰到不能update则直接跳出,newChildrenTop和oldChildrenTop会指向不能update的child位置

代码语言:javascript
复制
// Update the top of the list.
while ((oldChildrenTop <= oldChildrenBottom) && (newChildrenTop <= newChildrenBottom)) {
  //判断oldChild是否被移除
  final Element? oldChild = replaceWithNullIfForgotten(oldChildren[oldChildrenTop]);
  final Widget newWidget = newWidgets[newChildrenTop];
  //oldChild是空或者newWidget不能直接更新,就跳出
  if (oldChild == null || !Widget.canUpdate(oldChild.widget, newWidget))
    break;
  //更新child
  final Element newChild = updateChild(oldChild, newWidget, slotFor(newChildrenTop, previousChild))!;
  //设置到newChild中
  newChildren[newChildrenTop] = newChild;
  //设置上一个child
  previousChild = newChild;
  //移动到下一个位置
  newChildrenTop += 1;
  oldChildrenTop += 1;
}

5.3 从底部开始遍历判断canUpdate,知道返回false,跳出,这样oldChildrenBottom和newChildrenBottom指向末尾不能update的child

代码语言:javascript
复制
// Scan the bottom of the list.
while ((oldChildrenTop <= oldChildrenBottom) && (newChildrenTop <= newChildrenBottom)) {
  final Element? oldChild = replaceWithNullIfForgotten(oldChildren[oldChildrenBottom]);
  final Widget newWidget = newWidgets[newChildrenBottom];
  if (oldChild == null || !Widget.canUpdate(oldChild.widget, newWidget))
    break;
  //注意这里和step2的区别是没有去设置previousChild了,并且没有updateChild
  oldChildrenBottom -= 1;
  newChildrenBottom -= 1;
}

5.4 从oldChildrenTop开始遍历oldChildren,取出widget.key不为null的child,存入oldKeyedChildren,后面可能取出进行复用,这里oldChildrenTop应该等于oldChildrenBottom+1

代码语言:javascript
复制
// Scan the old children in the middle of the list.
// 根据top和bottom位置判断是否还存在中间的元素没有处理
final bool haveOldChildren = oldChildrenTop <= oldChildrenBottom;
//用于存放有key的old child
Map<Key, Element>? oldKeyedChildren;
if (haveOldChildren) {
  oldKeyedChildren = <Key, Element>{};
  //从顶部开始遍历oldChildren
  while (oldChildrenTop <= oldChildrenBottom) {
    final Element? oldChild = replaceWithNullIfForgotten(oldChildren[oldChildrenTop]);
    if (oldChild != null) {
      //如果有key,则存入oldKeyedChildren
      if (oldChild.widget.key != null)
        oldKeyedChildren[oldChild.widget.key!] = oldChild;
      else
        //没有直接废弃oldChild
        deactivateChild(oldChild);
    }
    //注意:这里是old child的位置移动
    oldChildrenTop += 1;
  }
}

5.5 从newChildrenTop开始遍历newWidgets,根据key从oldKeyedChildren取出old child,然后判断是否可以直接update,如果可以则在updateChild的作为oldChild参数传入,否则传null。到这里newChildrenTop应该等于newChildrenBottom+1

代码语言:javascript
复制
// Update the middle of the list.
//从顶部更新newChildren
while (newChildrenTop <= newChildrenBottom) {
  Element? oldChild;
  final Widget newWidget = newWidgets[newChildrenTop];
  if (haveOldChildren) {
    final Key? key = newWidget.key;
    //判断new child是否有key
    if (key != null) {
      //获取old child有相同key的child
      oldChild = oldKeyedChildren![key];
      if (oldChild != null) {
        //如果可以更新则直接更新
        if (Widget.canUpdate(oldChild.widget, newWidget)) {
          // we found a match!
          // remove it from oldKeyedChildren so we don't unsync it later
          //如果可以更新就可以移除掉了
          oldKeyedChildren.remove(key);
        } else {
          // Not a match, let's pretend we didn't see it for now.
          oldChild = null;
        }
      }
    }
  }
  //更新
  final Element newChild = updateChild(oldChild, newWidget, slotFor(newChildrenTop, previousChild))!;
  newChildren[newChildrenTop] = newChild;
  previousChild = newChild;
  newChildrenTop += 1;
}

5.6 在4.3中只是做了newChildrenBottom和oldChildrenBottom的标记,并没有真正的updateChild,所以。下面重置newChildrenBottom和oldChildrenBottom。继续从oldChildrenTop开始遍历,然后updateChild

代码语言:javascript
复制
// We've scanned the whole list.
//重置bottom位置
newChildrenBottom = newWidgets.length - 1;
oldChildrenBottom = oldChildren.length - 1;

// Update the bottom of the list.
while ((oldChildrenTop <= oldChildrenBottom) && (newChildrenTop <= newChildrenBottom)) {
  final Element oldChild = oldChildren[oldChildrenTop];
  final Widget newWidget = newWidgets[newChildrenTop];
  //更新剩余的child
  final Element newChild = updateChild(oldChild, newWidget, slotFor(newChildrenTop, previousChild))!;
  newChildren[newChildrenTop] = newChild;
  previousChild = newChild;
  newChildrenTop += 1;
  oldChildrenTop += 1;
}

5.7 清理没有复用成功的child

代码语言:javascript
复制
  // Clean up any of the remaining middle nodes from the old list.
  if (haveOldChildren && oldKeyedChildren!.isNotEmpty) {
    for (final Element oldChild in oldKeyedChildren.values) {
      //将剩下带有key的old child,同时又没能复用的child进行clean
      if (forgottenChildren == null || !forgottenChildren.contains(oldChild))
        deactivateChild(oldChild);
    }
  }
  return newChildren;
}

总结:这么长的流程和逻辑主要是为了对多个children的情况要进行判断是否可以复用,对于不能复用的child进行清理,最终针对child还是会执行到updateChild这样又回到了3

6 inflateWidget

Element中定义,在第3节中 updateChild如果old child是空或者无法update就需要inflateWidget

写android的朋友应该很熟悉了,android里有LayoutInflater.from().inflate(),从xml来解析获取到View;同样在这里通过widget来解析返回Element。关于GlobalKey的逻辑,我们先忽略,后面再介绍。下面的逻辑就简单了,创建一个element,然后mount到当前element

代码语言:javascript
复制
Element inflateWidget(Widget newWidget, Object? newSlot) {
  final Key? key = newWidget.key;
  if (key is GlobalKey) {
    final Element? newChild = _retakeInactiveElement(key, newWidget);
    if (newChild != null) {
      newChild._activateWithParent(this, newSlot);
      final Element? updatedChild = updateChild(newChild, newWidget, newSlot);
      return updatedChild!;
    }
  }
  //创建子Element
  final Element newChild = newWidget.createElement();
  //将child挂载到当前element
  newChild.mount(this, newSlot);
  //返回child element
  return newChild;
}

7 总结

走完上面整个rebuild流程,第一感受就是在于ComponentElement和RenderObjectElement在流程上有明显的区别,这也回到这两类Element的设计,RenderObjectElement不一定包含子child,但它包括renderObject用于渲染,而ComponentElement是一种组成的Element,它并不包含RenderObject,但它会有子 Element。因此在rebuild时ComponentElement只需要关心child的update,而RenderObjectElement还需要关注RenderObject的更新。另外在多child的情况如:第5节,diff的逻辑会稍微复杂一点。

对于整个流程中的关于方法我们也要熟悉,如:update,inflateWidget,updateChild。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2022.01.10 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1 performRebuild()
    • 1.1 RenderObjectElement
      • 1.1.1 SliverMultiBoxAdaptorElement
    • 1.2 ComponentElement
      • 1.2.1 StatelessElement
      • 1.2.2 StatefulElement
  • 2 build()
    • 2.1 ComponentElement
      • 2.2 SliverMultiBoxAdaptorElement
      • 3 updateChild
        • 3.1 Element/ComponentElement
          • 3.2 SliverMultiBoxAdaptorElement
          • 4 update
            • 4.1 StatelessElement
              • 4.2 StatefulElement
                • 4.3 RenderObjectElement
                  • 4.3.1 SingleChildRenderObjectElement
                  • 4.3.2 MutliChildRenderObjectElement
              • 5 updateChildren
                • 5.1 定义新旧children的开始和结束位置,用于后面遍历
                  • 5.2 从开始位置遍历children,主要处理可以直接updateChild的情况,碰到不能update则直接跳出,newChildrenTop和oldChildrenTop会指向不能update的child位置
                    • 5.3 从底部开始遍历判断canUpdate,知道返回false,跳出,这样oldChildrenBottom和newChildrenBottom指向末尾不能update的child
                      • 5.4 从oldChildrenTop开始遍历oldChildren,取出widget.key不为null的child,存入oldKeyedChildren,后面可能取出进行复用,这里oldChildrenTop应该等于oldChildrenBottom+1
                        • 5.5 从newChildrenTop开始遍历newWidgets,根据key从oldKeyedChildren取出old child,然后判断是否可以直接update,如果可以则在updateChild的作为oldChild参数传入,否则传null。到这里newChildrenTop应该等于newChildrenBottom+1
                          • 5.6 在4.3中只是做了newChildrenBottom和oldChildrenBottom的标记,并没有真正的updateChild,所以。下面重置newChildrenBottom和oldChildrenBottom。继续从oldChildrenTop开始遍历,然后updateChild
                            • 5.7 清理没有复用成功的child
                            • 6 inflateWidget
                            • 7 总结
                            领券
                            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档