Flutter笔记-控件更新

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

控件更新

前面我们知道runApp后会经过build()->performLayout()->paint(),那么StatefulWidget 经过 setState又会经过哪些流程?

setState流程.png


onBuildScheduled()是一个VoidCallback(无返回类型的函数),在WidgetsBinding中进行了赋值

//WidgetsBinding
buildOwner.onBuildScheduled = _handleBuildScheduled;

最后调用的是native Window_scheduleFrame方法,之后要去c++源码里寻找答案了
总之,最后会调用drawFrame()方法:

@protected
void drawFrame() {
  assert(renderView != null);
  pipelineOwner.flushLayout(); //调用RenderObject的performLayout();
  pipelineOwner.flushCompositingBits();
  pipelineOwner.flushPaint();  //调用RenderObject的paint(PaintingContext context, Offset offset)
  renderView.compositeFrame(); 
  pipelineOwner.flushSemantics(); 
}

所以对应的就有四个方法

markNeedsLayout();
markNeedsCompositingBitsUpdate();
markNeedsPaint();
markNeedsSemanticsUpdate();

他们本质还是会调用drawFrame(),但是通过不同的标识,并不会进行自己对应方法之外的操作。


测量与摆放流程
通过drawFrame()查看其的flushLayout()

void flushLayout() {
    profile(() {
      Timeline.startSync('Layout', arguments: timelineWhitelistArguments);
    });
    assert(() {
      _debugDoingLayout = true;
      return true;
    }());
    try {
      // TODO(ianh): assert that we're not allowing previously dirty nodes to redirty themselves
      while (_nodesNeedingLayout.isNotEmpty) {
        final List<RenderObject> dirtyNodes = _nodesNeedingLayout;
        _nodesNeedingLayout = <RenderObject>[];
        for (RenderObject node in dirtyNodes..sort((RenderObject a, RenderObject b) => a.depth - b.depth)) {
          if (node._needsLayout && node.owner == this)
            node._layoutWithoutResize(); //摆放
        }
      }
    } finally {
      assert(() {
        _debugDoingLayout = false;
        return true;
      }());
      profile(() {
        Timeline.finishSync();
      });
    }
  }

node就是一个RenderObject,然后执行他的_layoutWithoutResize()

  void _layoutWithoutResize() {
    assert(_relayoutBoundary == this);
    RenderObject debugPreviousActiveLayout;
    assert(!_debugMutationsLocked);
    assert(!_doingThisLayoutWithCallback);
    assert(_debugCanParentUseSize != null);
    assert(() {
      _debugMutationsLocked = true;
      _debugDoingThisLayout = true;
      debugPreviousActiveLayout = _debugActiveLayout;
      _debugActiveLayout = this;
      if (debugPrintLayouts)
        debugPrint('Laying out (without resize) $this');
      return true;
    }());
    try {
      //找到了,执行摆放
      performLayout();
      markNeedsSemanticsUpdate();
    } catch (e, stack) {
      _debugReportException('performLayout', e, stack);
    }
    assert(() {
      _debugActiveLayout = debugPreviousActiveLayout;
      _debugDoingThisLayout = false;
      _debugMutationsLocked = false;
      return true;
    }());
    _needsLayout = false;
    markNeedsPaint();
  }

很明显,这里并没有执行performResize(),这里我们换个切入点,我们从runApp(rootWidget)开始

扫描二维码关注公众号,回复: 6528454 查看本文章
void runApp(Widget app) {
  WidgetsFlutterBinding.ensureInitialized()
    ..attachRootWidget(app)
    ..scheduleWarmUpFrame();
}

ensureInitialized(),这个是一系列的绑定操作,直接看attachRootWidget(app),将我们的app控件绑定在renderView上

Element get renderViewElement => _renderViewElement;
Element _renderViewElement;
void attachRootWidget(Widget rootWidget) {
    _renderViewElement = RenderObjectToWidgetAdapter<RenderBox>(
      container: renderView,
      debugShortDescription: '[root]',
      child: rootWidget
    ).attachToRenderTree(buildOwner, renderViewElement); 
  }

RenderObjectToWidgetAdapter是一个RenderObjectWidget,我们直接看他的createRenderObject()看创建的对象是谁

class RenderObjectToWidgetAdapter<T extends RenderObject> extends RenderObjectWidget {
  RenderObjectToWidgetAdapter({
    this.child,
    this.container,
    this.debugShortDescription
  }) : super(key: GlobalObjectKey(container));
  //省略部分代码
  ... 
  final Widget child;
  final RenderObjectWithChildMixin<T> container;

  //关键点,返回的是一个container,即上面传递的renderView
  @override
  RenderObjectWithChildMixin<T> createRenderObject(BuildContext context) => container;

  @override
  void updateRenderObject(BuildContext context, RenderObject renderObject) { }

  RenderObjectToWidgetElement<T> attachToRenderTree(BuildOwner owner, [RenderObjectToWidgetElement<T> element]) {
    //第一次是null,会创建,随后都是复用这个已经创建的element
    if (element == null) {
      owner.lockState(() {
        element = createElement();
        assert(element != null);
        element.assignOwner(owner);
      });
      owner.buildScope(element, () {
        //执行build()
        element.mount(null, null);
      });
    } else {
      element._newWidget = this;
      //重新bulid()
      element.markNeedsBuild();
    }
    return element;
  }
}

而renderView是一个RenderView控件

class RenderView extends RenderObject with RenderObjectWithChildMixin<RenderBox> {
  RenderView({
    RenderBox child,
    @required ViewConfiguration configuration,
  }) : assert(configuration != null),
       _configuration = configuration {
    this.child = child;
  }

  @override
  void performResize() {
    assert(false);
  }

  @override
  void performLayout() {
    assert(_rootTransform != null);
    _size = configuration.size;
    assert(_size.isFinite);

    if (child != null)
      //执行子控件(即自己的app控件)的layout()方法
      child.layout(BoxConstraints.tight(_size));
  }
  //省略了大段内容
  ...
}

layout的流程是相同的,不需要重写

void layout(Constraints constraints, { bool parentUsesSize = false }) {
    ...
    if (sizedByParent) {
      assert(() { _debugDoingThisResize = true; return true; }());
      try {
        performResize(); //1.
        assert(() { debugAssertDoesMeetConstraints(); return true; }());
      } catch (e, stack) {
        _debugReportException('performResize', e, stack);
      }
      assert(() { _debugDoingThisResize = false; return true; }());
    }
    RenderObject debugPreviousActiveLayout;
    assert(() {
      _debugDoingThisLayout = true;
      debugPreviousActiveLayout = _debugActiveLayout;
      _debugActiveLayout = this;
      return true;
    }());
    try {
      performLayout(); //2.
      markNeedsSemanticsUpdate();
      assert(() { debugAssertDoesMeetConstraints(); return true; }());
    } catch (e, stack) {
      _debugReportException('performLayout', e, stack);
    }
    assert(() {
      _debugActiveLayout = debugPreviousActiveLayout;
      _debugDoingThisLayout = false;
      _debugMutationsLocked = false;
      return true;
    }());
    _needsLayout = false;
    markNeedsPaint();
  }

当sizedByParent为true时,才会执行performResize()。默认是false的,除非继承RenderObject,将其修改为true

@override
bool get sizedByParent => true;
@override
void performResize() {
  size = constraints.smallest;
}

大部分的布局控件一般都在performLayout()一起完成了测量和摆放。
sizedByParent 意为该节点的大小是否仅通过 parent 传给它的 constraints 就可以确定了,即该节点的大小与它自身的属性和其子节点无关,比如如果一个控件永远充满 parent 的大小,那么 sizedByParent就应该返回true,此时其大小在 performResize() 中就确定了,在后面的 performLayout() 方法中将不会再被修改了,这种情况下 performLayout() 只负责布局子节点

树的更新规则

  1. 找到widget对应的element节点,设置element为dirty,触发drawframe, drawframe会调用element的performRebuild()进行树重建
  2. widget.build() == null, deactive element.child,删除子树,流程结束
  3. element.child.widget == NULL, mount 的新子树,流程结束
  4. element.child.widget == widget.build() 无需重建,否则进入流程5
  5. Widget.canUpdate(element.child.widget, newWidget) == true,更新child的slot,element.child.update(newWidget)(如果child还有子节点,则递归上面的流程进行子树更新),流程结束,否则转6
  6. Widget.canUpdate(element.child.widget, newWidget) != true(widget的classtype 或者 key 不相等),deactivew element.child,mount 新子树

注意事项:

  1. element.child.widget == widget.build(),不会触发子树的update,当触发update的时候,如果没有生效,要注意widget是否使用旧widget,没有new widget,导致update流程走到该widget就停止了
  2. 子树的深度变化,会引起子树重建,如果子树是一个复杂度很高的树,可以使用GlobalKey做为子树widget的key。GlobalKey具有缓存功能

如何触发树更新

  1. 全局更新:调用runApp(rootWidget),一般flutter启动时调用后不再会调用
  2. 局部子树更新, 将该子树做StatefullWidget的一个子widget,并创建对应的State类实例,通过调用state.setState() 触发该子树的刷新

猜你喜欢

转载自blog.csdn.net/qq_27981847/article/details/88302251