Flutter是如何渲染Widget的
Flutter是Google开发的构建高性能UI界面的框架。在Flutter中一切都是Widget
渲染组成
Widget
Widget是对用户界面的一种不可变的描述。由于Widget是不可变的,当App进行交互的时候,我们的界面要进行变动,是怎么做到的呢?
实际上,Flutter由3种树Widget Tree、Element Tree和RenderObject Tree。Flutter通过结合这三颗树,更加高效的渲染UI界面。
使用Widget,可以声明我们想要的UI样式,配置样式属性信息。可以说是Widget是Element的一个配置描述。Widget会被映射为一个Element对象。
Widget它们本身没有可变的状态,如果我们想让Widget有可变的状态,可以使用StatefulWidget,StatefulWidget会创建一个State对象,并将State对象插入到Element树中。
同一个Widget可能会被多次移除和添加,同样的,同一个Element对象也会被多次在Element树中移除和添加。
Element
Element是用来描述Widget在树中位置的,记录了其上下级关系。它是可变的,用来管理UI的更新和变化。我们可以认为Element是来管理Widget的生命周期的。实际上Widget是没有生命周期的,Element有生命周期的,Element的生命周期也代表了Widget对象是否还在树中。
RenderObject
RenderObject负责设置大小,布局和绘制UI的,Flutter通过参照Render tree进行UI绘制。
通过以上我们知道由Widget负责配置信息,Element负责生命周期,RenderObject负责绘制。那么Flutter渲染Widget也分为3部分:设置配置信息,控制生命周期和绘制。

- 1,通过设置
Widget的properties和method,来配置UI的信息。 - 2,通过
Element来控制UI对象的生命周期,并且管理Element对象的父子关系。 - 3,通过
RenderObject,每个componet都有paint方法,通过约束和大小确定位置关系,并开始绘制

Source Step By Step
3颗树的创建流程
我们来看如下代码:
Widget build(BuildContext context) {
return Center(
child: RichText(
text: const TextSpan(text: "Rich Text"),
),
);
}
当创建RichTextWidget的时候,
1,会创建 MultiChildRenderObjectElement对象。
class RichText extends MultiChildRenderObjectWidget {
//....
}
abstract class MultiChildRenderObjectWidget extends RenderObjectWidget {
....
@override
MultiChildRenderObjectElement createElement() => MultiChildRenderObjectElement(this);
}
2,第二步,在mount方法中,创建RenderObject对象。
abstract class RenderObjectElement extends Element {
......
@override
void mount(Element? parent, Object? newSlot) {
super.mount(parent, newSlot);
......
_renderObject = widget.createRenderObject(this);
......
attachRenderObject(newSlot);
_dirty = false;
}
2.1,将Widget中的配置信息(属性),赋值给RenderObject对象。
MultiChildRenderObjectWidget:
@override
RenderParagraph createRenderObject(BuildContext context) {
assert(textDirection != null || debugCheckHasDirectionality(context));
return RenderParagraph(text,
textAlign: textAlign,
textDirection: textDirection ?? Directionality.of(context),
softWrap: softWrap,
overflow: overflow,
textScaleFactor: textScaleFactor,
maxLines: maxLines,
strutStyle: strutStyle,
textWidthBasis: textWidthBasis,
textHeightBehavior: textHeightBehavior,
locale: locale ?? Localizations.maybeLocaleOf(context),
);
}
通过以上步骤,我们就得到了MultiChildRenderObjectWidget、MultiChildRenderObjectElement 和 RenderParagraph
更新子部件
有时候我们需要根据不同的状态显示不同UI界面,这时候,就会去更新子部件,我们看如下代码
Widget build(BuildContext context) {
return Center(
child: RichText(
text: reverse
? const TextSpan(text: "Rich Text Two")
: const TextSpan(text: "Rich Text"),
),
);
}
当 reverse值更改的时候,在下一次rebuild()的过程中,将
TextSpan(text: "Rich Text Two")加入到Widget树,将原来的TextSpan从Widget树中移除,与相同位置的Widget进行比较,通过canUpdate方法判断是否可以更新
Widget:
static bool canUpdate(Widget oldWidget, Widget newWidget) {
return oldWidget.runtimeType == newWidget.runtimeType
&& oldWidget.key == newWidget.key;
}
两个TextSpan具有相同的runtimeType,且他俩的key值都为null,返回Yes。
然后执行 widget.updateRenderObject(this, renderObject); 更新RenderObject信息。
Widget:
@override
void updateRenderObject(BuildContext context, RenderParagraph renderObject) {
assert(textDirection != null || debugCheckHasDirectionality(context));
renderObject
..text = text
..textAlign = textAlign
..textDirection = textDirection ?? Directionality.of(context)
..softWrap = softWrap
..overflow = overflow
..textScaleFactor = textScaleFactor
..maxLines = maxLines
..strutStyle = strutStyle
..textWidthBasis = textWidthBasis
..textHeightBehavior = textHeightBehavior
..locale = locale ?? Localizations.maybeLocaleOf(context);
}
这样,RenderObject树中的信息就会被更新。通过重用RenderObject对象,就大大的提高了效率。毕竟,新建一个对象的开销非常大。
参考
转载自:https://juejin.cn/post/7094933420979175438