从源码了解Flutter的渲染基础:Widget/Element/RenderObject

栏目: IOS · 发布时间: 5年前

内容简介:原文请访问我的博客如果你要使用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 多了 SingleChildRenderObjectWidgetRenderObject 这层关系,那这层关系到底是干什么的呢?我们接着往下看源码。

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的时候才会执行,所以只有一次。

参考文献:

1. Flutter, what are Widgets, RenderObjects and Elements?

2. The Layer Cake


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

代码

代码

劳伦斯・莱斯格 / 李旭 / 中信出版社 / 2004-10-1 / 30.00元

劳伦斯·莱斯格的著作《代码》 问世便震动了学界和业界,被人称为“也许是迄今为止互联网领域最重要的书籍”,也被一些学者称为“网络空间法律的圣经”。 《代码》挑战了早期人们对互联网的认识,即技术已经创造了一个自由的环境,因而网络空间无法被规制——也就是说,网络的特性使它押脱了政府的控制。莱斯格提出,事实恰恰相反。 代码的存在证明,网络并不是本制拷贝 ,不可规制的,它并没有什......一起来看看 《代码》 这本书的介绍吧!

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码

SHA 加密
SHA 加密

SHA 加密工具

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具