控件更新
前面我们知道runApp后会经过build()->performLayout()->paint(),那么StatefulWidget 经过 setState又会经过哪些流程?
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)开始
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() 只负责布局子节点
树的更新规则
- 找到widget对应的element节点,设置element为dirty,触发drawframe, drawframe会调用element的performRebuild()进行树重建
- widget.build() == null, deactive element.child,删除子树,流程结束
- element.child.widget == NULL, mount 的新子树,流程结束
- element.child.widget == widget.build() 无需重建,否则进入流程5
- Widget.canUpdate(element.child.widget, newWidget) == true,更新child的slot,element.child.update(newWidget)(如果child还有子节点,则递归上面的流程进行子树更新),流程结束,否则转6
- Widget.canUpdate(element.child.widget, newWidget) != true(widget的classtype 或者 key 不相等),deactivew element.child,mount 新子树
注意事项:
- element.child.widget == widget.build(),不会触发子树的update,当触发update的时候,如果没有生效,要注意widget是否使用旧widget,没有new widget,导致update流程走到该widget就停止了
- 子树的深度变化,会引起子树重建,如果子树是一个复杂度很高的树,可以使用GlobalKey做为子树widget的key。GlobalKey具有缓存功能
如何触发树更新
- 全局更新:调用runApp(rootWidget),一般flutter启动时调用后不再会调用
- 局部子树更新, 将该子树做StatefullWidget的一个子widget,并创建对应的State类实例,通过调用state.setState() 触发该子树的刷新
网友评论