9

原文在这里

本文详细解释了如何把Widget转化成了屏幕上的一个个像素点

想要成为一个更好的开发,了解底层的实现技术几乎是必不可少的。你可以更容易的创建自定义的布局和特效,如果你学习了这些底层技术是如何工作的。也可以让少在电脑前加几个晚上的班。

本文的目的就是要介绍Flutter之下的技术,并让你理解他们的是如何运行的。

## 我们现在开始

你也许已经知道如何使用StatelessWidget和StatefulWidget。但是这些Widget只是把其他的Widget组合到了一起。在屏幕加上的布局和绘制是发生在别的地方的。

**我强烈建议你打开你最心爱的编辑工具,然后跟着本文一步一步的查看实际代码是如何实现的并一路“原来如此”过去。、

## Opacity组件
从Opacity这个组件开始是最好不过了。这个Widget足够的简单,而且是一个绝佳的例子。

它只接受一个子组件。因此你可以把任何一个widget放进Opacity里改变其显示。另外还有一个值opacity,在0.0和1.0之间。它用来控制透明度。

OpacitySingleChildRenderObjectWidget的子类。继承的路径是这样的:

Opacity -> SingleChildRenderObjectWidget -> RenderObjectWidget -> Widget。

StatelessWidgetStatefulWidget是这样的继承路径:

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组件工作的原理。

我的目标是用这篇文章来介绍组件之下的原理。还有很多的内容没有覆盖到,但是我希望这篇文章至少可以让你初窥门径。


小红星闪啊闪
914 声望1.9k 粉丝

时不我待