内容简介:原文请访问我的博客如果你要使用Flutter开始构建一个页面,那么你可能从Widget开始一层一层搭建。但是你曾经想过这些Widget是如何绘制到屏幕上的吗?很多同学应该会熟悉Flutter的三个树形结构:
转载请联系: 微信号: michaelzhoujay
原文请访问我的博客
如果你要使用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的时候才会执行,所以只有一次。
参考文献:
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Head First HTML and CSS
Elisabeth Robson、Eric Freeman / O'Reilly Media / 2012-9-8 / USD 39.99
Tired of reading HTML books that only make sense after you're an expert? Then it's about time you picked up Head First HTML and really learned HTML. You want to learn HTML so you can finally create th......一起来看看 《Head First HTML and CSS》 这本书的介绍吧!