原文在这里。
本文详细解释了如何把Widget转化成了屏幕上的一个个像素点
想要成为一个更好的开发,了解底层的实现技术几乎是必不可少的。你可以更容易的创建自定义的布局和特效,如果你学习了这些底层技术是如何工作的。也可以让少在电脑前加几个晚上的班。
本文的目的就是要介绍Flutter之下的技术,并让你理解他们的是如何运行的。
## 我们现在开始
你也许已经知道如何使用StatelessWidget和StatefulWidget。但是这些Widget只是把其他的Widget组合到了一起。在屏幕加上的布局和绘制是发生在别的地方的。
**我强烈建议你打开你最心爱的编辑工具,然后跟着本文一步一步的查看实际代码是如何实现的并一路“原来如此”过去。、
## Opacity组件
从Opacity这个组件开始是最好不过了。这个Widget足够的简单,而且是一个绝佳的例子。
它只接受一个子组件。因此你可以把任何一个widget放进Opacity里改变其显示。另外还有一个值opacity
,在0.0和1.0之间。它用来控制透明度。
Opacity
是SingleChildRenderObjectWidget
的子类。继承的路径是这样的:
Opacity -> SingleChildRenderObjectWidget -> RenderObjectWidget -> Widget。
而StatelessWidget
和StatefulWidget
是这样的继承路径:
StatelessWidget/StatefulWidget -> Widget
不同之处就在于StatelessWidget和StatefulWidget只是把不同的组件(Widget)组合在一起,而Opacity组件会控制一个组件如何绘制。
但是如果你想从Opacity的代码里找到一些绘制像素的线索,这几乎是徒劳的。这是因为一个Widget只包含了配置信息,比如Opacity
组件只包含了一个opacity的值。
这就是为什么你可以在一个组件的
build
方法里创建新的Widget,里面并不会包含耗费资源的构建组件的代码,仅仅是包含了一些构建需要的信息。
绘制
绘制的时候会发生什么呢?
RenderObject
绘制都在RenderObject
里进行。这个单从名称里也可以知道了。Opacit组件这样创建和更新RenderObject:
@override
RenderOpacity createRenderObject(BuildContext context) => new RenderOpacity(opacity: opacity);
@override
void updateRenderObject(BuildContext context, RenderOpacity renderObject) {
renderObject.opacity = opacity;
}
RenderOpacity
Opacity
组件把自身的size和子组件的size设定成一样的值。它基本上和它子组件的每一方面都一样,除了绘制。在绘制子组件之前添加了opacity值。
在这个情况下, RenderOpacity
需要实现所有的方法(比如,执行布局、碰撞检测和计算size)然后交给子类去执行具体的工作。
RenderOpacity
继承了RenderProxyBox
(这个类mixin了一些其他 的类)。这些类都分别实现了上面提到的那些方法。
double get opacity => _opacity;
double _opacity;
set opacity(double value) {
_opacity = value;
markNeedsPaint();
}
我(作者)删除了大部分的代码,要看全部代码可以在这里看。
getter把私有的值暴露出去。setter里面会调用markNeedsPaint()
或者markNeedsLayout()
。就如名字所言,它会通知系统“这个组件发生了改变,需要重绘或者重新布局”
在RenderOpacity
里面还有如下的代码:
@override
void paint(PaintingContext context, Offset offset) {
context.pushOpacity(offset, _alpha, super.paint);
}
PaintingContext
就是一个画布。在这个画布上有一个方法叫做pushOpacity
!
这一行就是opacity的具体实现。
回顾
-
Opacity
不是一个StatelessWidget
或者StatefullWidget
而是一个SingleChildRendeObjectWidget
。 - 组件只包含了绘制的时候需要用到的信息。比如,
Opacity
组件包含了一个opacity值。 -
RenderOpacity
,继承自RenderProxyBox
,执行了具体的布局和绘制动作 - 因为Opacity组件和它的子组件基本上完全一样,所以它代理了其子组件的所有方法
- 它override了绘制(paint)方法,这个方法会给Opacity的子组件添加一个特定的opacity值
就这样了么?
记住组件(widget)只不过是一个配置,RenderObject
才是管理布局和绘制的。
在Flutter里,你基本上一直都会创建新的Widget,当build()
方法被调用的时候你就创建了一大堆的组件。当某些变化发生的时候,build方法基本上都会被调用。比如一个动画里,build方法更会被经常调用。最好是不要每次都重新绘制整个子树,更新会更好一些。
你不能获得一个组件在屏幕上的大小或者位置,因为一个组件姿势一个蓝图,并不是实际绘制在界面上的。它只包含了render object需要用的信息。
Element
Element是一个组件树的实际组件。
发生了什么
第一次一个组件被创建的时候,会有一个Element被创建,并且两者互相关联。之后这个element被插入到了一个树里。如果组件(widget)发生了更改,它会和旧的组件对比,并对应的更新element。最重要的是在这个时候element不会重新创建,只会被更新。
Element是flutter核心的一部分,不过现在不需要知道更多的内容。这些就足够了。
Opacity组件的Element是在哪里创建的
请好奇的同学看过来。在SingleChildRenderObectWidget
里
@override
SingleChildRenderObjectElement createElement() => new SingleChildRenderObjectElement(this);
SingleChildRenderObjectElement
也只是一个有一个child的Element。
Element创建RenderObject
如果是Element创建了RenderObject,那么Opacity组件的RenderObject怎么是自己创建的呢?
基本上是因为Opacity组件只是需要一个RenderObject
但是不需要一个定制的Element。
看下面的代码:
SingleChildRenderObjectElement(SingleChildRenderObjectWidget widget) : super(widget);
SingleChildRenderObjectElement
有一个RenderObjectWidget
的引用(这里也包含了创建RenderObject
的方法)。
在RenderObjectElement#mount
方法里element被插入到了element树里:
@override
void mount(Element parent, dynamic newSlot) {
super.mount(parent, newSlot);
_renderObject = widget.createRenderObject(this);
attachRenderObject(newSlot);
_dirty = false;
}
在Element挂载的时候,它才会让组件创建一个render object。
最后
这就是Opacity组件工作的原理。
我的目标是用这篇文章来介绍组件之下的原理。还有很多的内容没有覆盖到,但是我希望这篇文章至少可以让你初窥门径。