从源码了解Flutter的渲染基础:Widget/Element/RenderObject
如果你要使用Flutter开始构建一个页面,那么你可能从Widget开始一层一层搭建。
但是你曾经想过这些Widget是如何绘制到屏幕上的吗?很多同学应该会熟悉Flutter的三个树形结构:
- Widget Tree
- Element Tree
- RenderObject Tree
站在Flutter程序员的角度,打交道最多的应该是Widget了,那么下面我带领大家从Flutter源码读起来看一下一个Widget是如何绘制到屏幕上的。
Opacity
首先我们来回忆一个熟悉的Widget:Opacity
。
这个Widget一般用来给一个布局加上透明度,它只有一个参数 opacity
,1.0表示完全不透明,0.0表示完全透明。
在源码里可以看到 Opacity 的继承关系:
Opacity -> SingleChildRenderObjectWidget -> RenderObject -> Widget
我们比较一下最简单的 StatelessWidget/StatefulWidget :
StatelessWidget -> Widget
StatefulWidget -> Widget
可以看到Opacity
多了SingleChildRenderObjectWidget
和RenderObject
这层关系,那这层关系到底是干什么的呢?我们接着往下看源码。
createRenderObject 方法
通过看RenderObject
的代码,我们知道只要继承了 RenderObject
就需要实现createRenderObject
方法。
@override
RenderOpacity createRenderObject(BuildContext context) {
return RenderOpacity(
opacity: opacity,
alwaysIncludeSemantics: alwaysIncludeSemantics,
);
}
可以发现其实最终返回了一个RenderOpacity
RenderOpacity
大概看一下RenderOpacity
的代码,可以知道这里实现了最后的所谓的“绘制”也就是paint
的代码:
void paint(PaintingContext context, Offset offset) {
if (child != null) {
if (_alpha == 0) {
return;
}
if (_alpha == 255) {
context.paintChild(child, offset);
return;
}
assert(needsCompositing);
context.pushOpacity(offset, _alpha, super.paint);
}
}
这里的PaintingContext
实际上是个Canvas,所以最重要的代码就是context.pushOpacity
这个方法的调用,最终用上了 _alpha
,而这个_alpha
由最上层Opacity
Widget的唯一参数opacity
计算而来。
所以我们来总结一下这里面的关系:
Opacity
直接继承SingleChildRenderObjectWidget
Widget
不直接参与绘制,仅仅存储绘制需要的信息RenderOpacity
在这里承担了实际的 layout/render 逻辑
小结
我们日常所用的Widget仅仅是一些配置,RenderObject做了实际的 layout/render 等工作。这一点和Android的 View
、iOS的 UIView
有本质的区别,请注意。
在Flutter中,只要是build()
方法被调用了,就会创建一堆Widget,这个是OK的,上面说过Widget仅仅是配置文件,所以即使频繁地 创建/重建 它们,是不会带来界面的刷新的。
回想一下,如果你要在Flutter里创建一个动画,一般来说,需要在指定的Widget之上套上一层XXXTransition,然后动画开始后用animationController
来不断地让Widget刷新重建,但是整个页面不会因此重新刷新。
所以,Widget的重建和页面真正的重新渲染没有直接的联系,那么页面什么时候才会重新渲染呢,我们接着看源码。
Element
上面我们以Opacity
为例,从源码中看出了它的继承关系,并且推导出一些结论,这里面都没有涉及到Element
,但实际上Element也十分重要。那什么是Element呢?源码里的注释如下:
An instantiation of a Widget at a particular location in the tree.
我们知道,Widget是immutable的,也就是说一旦有了变化,Widget是反复重建的,你在代码里写的Widget的构造函数会被重新执行。那对应的,肯定得有一个东西来消化掉这些变化中的不变,来做cache。
让程序员不去保存Widgets是好的,因为他不会因为管理无数的Widget而且烦恼,一旦有变化就当整个页面都在变化,Widget的每一帧对应了一个State。这样,一个指定的state就能精确描述一个Widget该怎么展示。也就是说,程序员只要管理好状态,那么就能管理好整个页面变化的逻辑。
当一个Widget首次被创建的时候,那么这个Widget会过Widget.createElement
inflate成一个element
,挂在 element tree 上。
此后,当State发生变化,则Widget重建,但Element只会updates。
也就是说我们能大概有一个以下的一个流程:
程序员写Widget -> Widget形成Element -> Element构建 RenderObject -> RenderObject描述Canvas绘制 -> 交给FlutterEngine 光栅化Rasterize
回到我们今天的主角Opacity
,从上面的分析,我们知道Opacity也是Widget,所以理应有对应的Element,但是上面看到为啥直接Widget就自己返回一个RenderOpacity
呢?看起来是Widget自己创建了RenderObject。那么它的element是什么时候创建的呢?看源码:
abstract class SingleChildRenderObjectWidget extends RenderObjectWidget {
...
@override
SingleChildRenderObjectElement createElement() => new SingleChildRenderObjectElement(this);
...
}
Opacity继承自SingleChildRenderObjectWidget
,
这里面的SingleChildRenderObjectElement
,实际上是只有一个child的Element。当第Widget第一次创建的时候,createElement
会被调用,返回一个SingleChildRenderObjectElement
那它的 RenderObject 实际上是被这个 Element 创建的,看一下为什么:
class SingleChildRenderObjectElement extends RenderObjectElement {
SingleChildRenderObjectElement(SingleChildRenderObjectWidget widget) : super(widget);
...
}
这个SingleChildRenderObjectElement
构造函数接受了一个SingleChildRenderObjectWidget
参数,它负责创建一个RenderObject
。那么我们看这个 renderobject 实际在哪里创建的:
class SingleChildRenderObjectElement {
...
@override
void mount(Element parent, dynamic newSlot) {
super.mount(parent, newSlot);
_child = updateChild(_child, widget.child, null);
}
...
}
SingleChildRenderObjectElement里有个mount方法,看起来没啥,不过没事儿我们往父类里看:
class RenderObjectElement extends Element {
...
@override
void mount(Element parent, dynamic newSlot) {
super.mount(parent, newSlot);
_renderObject = widget.createRenderObject(this);
assert(() { _debugUpdateRenderObjectOwner(); return true; }());
assert(_slot == newSlot);
attachRenderObject(newSlot);
_dirty = false;
}
...
终于看到magic了,所以在mount
方法里,element调用了widget.createRenderObject
,并attach。
注意,这些都发生在mount
方法里,意味着只有element mounted的时候才会执行,所以只有一次。
参考文献:
Written on June 28, 2019
转载请联系我,微信号: michaelzhoujay