结合前面几篇文章,今天我们例举一个简单的demo说明Flutter刷新机制
demo说明:
创建一个StatefulWidget 里面放置一个定时器,每间隔10s刷新一次,然后将容器背景改成随机颜色
import 'dart:async';
import 'dart:math';
import 'dart:ui';
import 'package:flutter/material.dart';
void main() {
runApp(DemoWidget());
}
class DemoWidget extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return DemoState();
}
}
class DemoState extends State {
int randomIndex = 0;
List<Color> colorArray = [Colors.white, Colors.yellowAccent, Colors.green, Colors.lightBlue, Colors.pink];
@override
void initState() {
Timer.periodic(Duration(seconds: 10), (timer) {
setState(() {
randomIndex = Random().nextInt(5);
});
});
super.initState();
}
@override
Widget build(BuildContext context) {
return Container(
width: 50,
height: 50,
color: colorArray.elementAt(randomIndex),
);
}
}
demo运行起来后,我们打开FlutterDevTools 工具,在浏览器能清晰看出组件树结构
可以看出我们的RenderObject Tree根节点时RenderView ,最后一个页节点是 RenderColoredBox
当定时器触发后调用setState,state重新build,color会变色,那这个是如何做到的呢
@protected
void setState(VoidCallback fn) {
...
_element!.markNeedsBuild();
}
void markNeedsBuild() {
...
if (dirty)
return;
_dirty = true;
owner!.scheduleBuildFor(this);
}
void scheduleBuildFor(Element element) {
...
if (!_scheduledFlushDirtyElements && onBuildScheduled != null) {
_scheduledFlushDirtyElements = true;
onBuildScheduled!();
}
_dirtyElements.add(element);
element._inDirtyList = true;
...
}
void _handleBuildScheduled() {
...
ensureVisualUpdate();
}
以上4个主要方法主要功能就是将当前element标记为dirty,放入到_dirtyElements列表备用。而后给底层引擎发送一个Vsync命令
当下一个Vsync信号驱动到Flutter ,进入_handleBeginFrame 而后进入drawFrame方法
@override
void drawFrame() {
...
try {
if (renderViewElement != null)
buildOwner!.buildScope(renderViewElement!);
super.drawFrame();
buildOwner!.finalizeTree();
} finally {
assert(() {
debugBuildingDirtyElements = false;
return true;
}());
}
...
}
buildOwner !.buildScope 这个方法就是build入口
void buildScope(Element context, [ VoidCallback? callback ]) {
...
try {
_dirtyElements[index].rebuild();
} catch (e, stack) {
}
...
}
可以看到buildScope会从_dirtyElements列表轮询去调用rebuild
void rebuild() {
...
performRebuild();
...
}
void performRebuild() {
...
Widget? built;
built = build();
try {
_child = updateChild(_child, built, slot);
assert(_child != null);
} catch (e, stack) {
...
}
...
}
performRebuild 会调用build会重新创建widget,然后调用updateChild方法
函数updateChild()
比较重要,用来更新一个孩子节点。更新有四种情况:
- 新
Widget
为空,老Widget
也为空。则啥也不做。 - 新
Widget
为空,老Widget
不为空。这个Element
被移除。 - 新
Widget
不为空,老Widget
为空。则调用inflateWidget()
以这个Wiget
为配置实例化一个Element
。 - 新
Widget
不为空,老Widget
不为空。调用update()
函数更新子Element
。update()
函数由子类实现。
abstract class RenderObjectElement extends Element {
/// Creates an element that uses the given widget as its configuration.
RenderObjectElement(RenderObjectWidget widget) : super(widget);
@override
RenderObjectWidget get widget => super.widget as RenderObjectWidget;
/// The underlying [RenderObject] for this element.
///
/// If this element has been [unmount]ed, this getter will throw.
@override
RenderObject get renderObject {
assert(_renderObject != null, '$runtimeType unmounted');
return _renderObject!;
}
RenderObject? _renderObject;
bool _debugDoingBuild = false;
@override
bool get debugDoingBuild => _debugDoingBuild;
RenderObjectElement? _ancestorRenderObjectElement;
...
@override
void update(covariant RenderObjectWidget newWidget) {
super.update(newWidget);
...
widget.updateRenderObject(this, renderObject);
...
_dirty = false;
}
}
RenderObjectElement 复写了update方法,最后在update方法中看到有widget.updateRenderObject(this, renderObject);这句就是更新RenderObject对象,相对于我们demo RenderColoredBox 就是我们这个变化的RenderObject,ColoredBox就是该RenderObject对应的Widget ,最后会调用到ColordBox的updateRenderObject方法去设置颜色
class ColoredBox extends SingleChildRenderObjectWidget {
/// Creates a widget that paints its area with the specified [Color].
///
/// The [color] parameter must not be null.
const ColoredBox({ required this.color, Widget? child, Key? key })
: assert(color != null),
super(key: key, child: child);
/// The color to paint the background area with.
final Color color;
@override
RenderObject createRenderObject(BuildContext context) {
return _RenderColoredBox(color: color);
}
@override
void updateRenderObject(BuildContext context, RenderObject renderObject) {
(renderObject as _RenderColoredBox).color = color;
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(DiagnosticsProperty<Color>('color', color));
}
}
set color(Color value) {
assert(value != null);
if (value == _color) {
return;
}
_color = value;
markNeedsPaint();
}
看到设置颜色后,重新调用了markNeedsPaint方法,标记当前element需要被重新paint,在后续绘制流过程中,当前element就会重绘