TOC
The Widget tree, Element tree, Rendering Object tree in flutter
The purpose of the three type of trees in Flutter:
-
Widget tree: hold the config;
-
Element tree: hold a spot in the UI hierarchy and manage parent/child relationship
-
Rendering object tree: size and paint itself, lay out children;
Entry-point binding.dart
void runApp(Widget app){
WidgetsFlutterBinding.ensureInitialized()
..attachRootWidget(app)
..scheduleWarmUpFrame();
}
Resouce: https://www.youtube.com/watch?v=996ZgFRENMs
At what point are the root nodes for these three tree types created?
This the source code of runApp
:
void runApp(Widget app){
WidgetsFlutterBinding.ensureInitialized()
..attachRootWidget(app)
..scheduleWarmUpFrame();
}
For the root node of the widget tree:
The build
method in State
or StatelessWidget
class in responsible for creating the widget and its child which is the widget tree.
The top node of the widget tree is the root of the widget tree and it will be passed to the runApp
method.
The root node of element was created in attachToRenderTree
method. In attachRootWidget
method, we can see that it calls attachToRenderTree
;
mixin WidgetBinding {
//...
void attachRootWidget(Widget rootWidget) {
final bool isBootstrapFrame = rootElement == null;
_readyToProduceFrames = true;
_rootElement = RenderObjectToWidgetAdapter<RenderBox>(
container: renderView,
debugShortDescription: '[root]',
child: rootWidget,
).attachToRenderTree(buildOwner!, rootElement as RenderObjectToWidgetElement<RenderBox>?);
if (isBootstrapFrame) {
SchedulerBinding.instance.ensureVisualUpdate();
}
}
//...
}
The source code of attachToRenderTree
:
/// Inflate this widget and actually set the resulting [RenderObject] as the
/// child of [container].
RenderObjectToWidgetElement<T> attachToRenderTree(BuildOwner owner, [ RenderObjectToWidgetElement<T>? element ]) {
if (element == null) {
owner.lockState(() {
element = createElement();
assert(element != null);
element!.assignOwner(owner);
});
owner.buildScope(element!, () {
element!.mount(null, null);
});
} else {
element._newWidget = this;
element.markNeedsBuild();
}
return element!;
}
The code element = createElement()
will create the root node of the element tree.
WidgetsBinding
extends BindingBase
, when creating the instance of WidgetsFlutterBinding (WidgetsFlutterBinding()
), it will trigger BindingBase()
which call initInstances()
; and initInstances()
abstract class BindingBase {
BindingBase() {
initInstances();
//..
initServiceExtensions();
//..
}
//...
}
class WidgetsFlutterBinding extends BindingBase with GestureBinding, SchedulerBinding, ServicesBinding, PaintingBinding, SemanticsBinding, RendererBinding, WidgetsBinding {
static WidgetsBinding ensureInitialized() {
if (WidgetsBinding._instance == null) {
WidgetsFlutterBinding();
}
return WidgetsBinding.instance;
}
}
In the process of WidgetsFlutterBinding.ensureInitialized()
, theRendererBinding
’s initInstances
method is also triggered somewhere. This method calls initRenderView()
to create RenderView
, which hosts the root node of the Rendering object;
When are the child node of these three trees going to be updated?
For widget trees, when we update the text of a TextView or add new children to a Row, the configurations of these widgets (such as TextView or Row) will be updated as well. This is how the widget tree get updated.
For element trees, when setState(() {});
was called, it will request _element!.markNeedsBuild()
method and mark the corresponding element as _dirty
. The call chain is following:
Call chain:
-
User call
State.setState() -> BuildOwner.scheduleBuildFor(this);
; -
WidgetsBinding.drawFrame(Framework does this each frame) -> BuildOwner.buildScope -> Element.rebuild() -> Element.performRebuild() -> ComponentElement.performRebuild() -> (Sub ComponentElement Element).build()
For Element.rebuild()
:
- A [StatelessElement] rebuilds by using its widget’s [StatelessWidget.build] method.
- A [StatefulElement] rebuilds by using its widget’s state’s [State.build] method.
- A [RenderObjectElement] rebuilds by updating its [RenderObject].
When
abstract class State<T extends StatefulWidget> with Diagnosticable {
void setState(VoidCallback fn) {
//...
_element!.markNeedsBuild();
}
@protected
Widget build(BuildContext context);
}
abstract class Element extends DiagnosticableTree implements BuildContext {
void markNeedsBuild() {
//...
_dirty = true;
owner!.scheduleBuildFor(this);
}
//...
/// Called by the [BuildOwner] when [BuildOwner.scheduleBuildFor] has been
/// called to mark this element dirty, by [mount] when the element is first
/// built, and by [update] when the widget has changed.
@pragma('vm:prefer-inline')
void rebuild({bool force = false}) {
//...
performRebuild();
//...
}
}
abstract class ComponentElement extends Element {
/// Calls the [StatelessWidget.build] method of the [StatelessWidget] object
/// (for stateless widgets) or the [State.build] method of the [State] object
/// (for stateful widgets) and then updates the widget tree.
///
/// Called automatically during [mount] to generate the first build, and by
/// [rebuild] when the element needs updating.
@override
@pragma('vm:notify-debugger-on-exception')
void performRebuild() {
Widget? built;
//...
built = build();
//...
_child = updateChild(_child, built, slot);
//...
}
}
/// An [Element] that uses a [StatefulWidget] as its configuration.
class StatefulElement extends ComponentElement {
@override
void update(StatefulWidget newWidget) {
super.update(newWidget);
assert(widget == newWidget);
final StatefulWidget oldWidget = state._widget!;
state._widget = widget as StatefulWidget;
final Object? debugCheckForReturnedFuture = state.didUpdateWidget(oldWidget) as dynamic;
rebuild(force: true);
}
}
/// An [Element] that uses a [StatelessWidget] as its configuration.
class StatelessElement extends ComponentElement {
/// Creates an element that uses the given widget as its configuration.
StatelessElement(StatelessWidget super.widget);
@override
Widget build() => (widget as StatelessWidget).build(this);
@override
void update(StatelessWidget newWidget) {
super.update(newWidget);
assert(widget == newWidget);
rebuild(force: true);
}
}
abstract class RenderObjectElement extends Element {
@override
void update(covariant RenderObjectWidget newWidget) {
super.update(newWidget);
assert(widget == newWidget);
assert(() {
_debugUpdateRenderObjectOwner();
return true;
}());
_performRebuild(); // calls widget.updateRenderObject()
}
@pragma('vm:prefer-inline')
void _performRebuild() {
assert(() {
_debugDoingBuild = true;
return true;
}());
(widget as RenderObjectWidget).updateRenderObject(this, renderObject);
assert(() {
_debugDoingBuild = false;
return true;
}());
super.performRebuild(); // clears the "dirty" flag
}
}
file: flutter/lib/src/widgets/framework.dart
/// Manager class for the widgets framework.
class BuildOwner {
void scheduleBuildFor(Element element) {
//...
if (!_scheduledFlushDirtyElements && onBuildScheduled != null) {
_scheduledFlushDirtyElements = true;
onBuildScheduled!();
}
_dirtyElements.add(element);
//...
}
void buildScope(Element context, [ VoidCallback? callback ]) {
//...
element.rebuild();
//...
}
}
class RenderObjectToWidgetAdapter<T extends RenderObject> extends RenderObjectWidget {
RenderObjectToWidgetElement<T> attachToRenderTree(BuildOwner owner, [ RenderObjectToWidgetElement<T>? element ]) {
if (element == null) {
owner.lockState(() {
element = createElement();
assert(element != null);
element!.assignOwner(owner);
});
owner.buildScope(element!, () {
element!.mount(null, null);
});
} else {
element._newWidget = this;
element.markNeedsBuild();
}
return element!;
}
}
/// The glue between the widgets layer and the Flutter engine.
mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, RendererBinding, SemanticsBinding {
/// This method is called by [handleDrawFrame], which itself is called
/// automatically by the engine when it is time to lay out and paint a
/// frame.
@override
void drawFrame() {
//...
if (rootElement != null) {
buildOwner!.buildScope(rootElement!);
}
super.drawFrame();
buildOwner!.finalizeTree();
//...
}
}
Q&A
1.when does State.didUpdateWidget(AnimatedCrossFade oldWidget)
be called?
The didUpdateWidget
method is added in State
class. Called whenever the widget configuration changes.
After the initial build: The didUpdateWidget method is not called during the initial build of the widget. It is only called after the widget has been built at least once.
When the parent widget rebuilds: If the parent widget rebuilds (e.g., due to a state change or new data), it may create a new instance of the child widget with different arguments or properties. In this case, Flutter will call the didUpdateWidget method on the existing State object associated with the child widget, passing in the previous widget instance and the new widget instance.
When the widget’s properties change: If the widget’s properties or arguments change, even if the parent widget doesn’t rebuild, Flutter will still call didUpdateWidget on the existing State object, passing in the previous widget instance and the new widget instance.
The framework always calls [build] after calling [didUpdateWidget].
Call chain:
-
User call
State.setState() -> BuildOwner.scheduleBuildFor(this);
; -
WidgetsBinding.drawFrame(Framework does this each frame) -> BuildOwner.buildScope -> Element.rebuild() -> Element.performRebuild() -> ComponentElement.performRebuild() -> (Sub ComponentElement Element).build()
For Element.rebuild()
:
- A [StatelessElement] rebuilds by using its widget’s [StatelessWidget.build] method.
- A [StatefulElement] rebuilds by using its widget’s state’s [State.build] method.
- A [RenderObjectElement] rebuilds by updating its [RenderObject].
「点个赞」
点个赞
使用微信扫描二维码完成支付
