Flutter 是如何渲染的?

前言

要解答这个问题,首先须要认识到 Flutter 中有三棵树:Widget 树,Element 树和 RenderObject 树。html

当应用启动时 Flutter 会遍历并建立全部的 Widget 造成 Widget Tree,同时与 Widget Tree 相对应,经过调用 Widget 上的 createElement() 方法建立每一个 Element 对象,造成 Element Tree浏览器

最后调用 ElementcreateRenderObject() 方法建立每一个渲染对象,造成一个 Render Treemarkdown

而后须要知道 WidgetElementRenderObject 究竟是啥以及它们是干什么的。ide

什么是 Widget

Widget 是 Flutter 的核心部分,是用户界面的不可变描述信息。正如 Flutter 的口号 Everything’s a widget, 用 Flutter 开发应用就是在写 Widget 🐶。布局

Flutter 的 Widget 不仅表示 UI 控件,还表示一些功能性的组件,如路由跳转 Navigator,手势检测 GestureDetector 组件等。post

@immutable
abstract class Widget extends DiagnosticableTree {
  /// Initializes [key] for subclasses.
  const Widget({ this.key });
  final Key key;

  /// ...

  @protected
  Element createElement();

  /// ...

  static bool canUpdate(Widget oldWidget, Widget newWidget) {
    return oldWidget.runtimeType == newWidget.runtimeType
      && oldWidget.key == newWidget.key;
  }
}
复制代码

WidgetcanUpdate 方法经过比较新部件和旧部件的 runtimeTypekey 属性是否相同来决定更新部件对应的 Element性能

什么是 Element

Element 是实例化的 Widget 对象,经过 WidgetcreateElement() 方法,在特定位置使用 Widget 配置数据生成。ui

Element 用于管理应用 UI 的更新和更改,管理部件的生命周期,每一个 Element 都包含对 WidgetRenderObject 的引用。this

relationship

Widget 变化时,若是两个 WidgetruntimeTypekey 属性相同的,那么新的 Element 会经过 Element.update() 更新旧的 Element,不然旧的 Element 会被删除,新生成的 Element 插入到树中。spa

abstract class Element extends DiagnosticableTree implements BuildContext {
  /// Creates an element that uses the given widget as its configuration.
  ///
  /// Typically called by an override of [Widget.createElement].
  Element(Widget widget)
    : assert(widget != null),
      _widget = widget;

  /// Change the widget used to configure this element.
  ///
  /// The framework calls this function when the parent wishes to use a
  /// different widget to configure this element. The new widget is guaranteed
  /// to have the same [runtimeType] as the old widget.
  ///
  /// This function is called only during the "active" lifecycle state.
  @mustCallSuper
  void update(covariant Widget newWidget) {
    /// ...
  }

  /// Creates an instance of the [RenderObject] class that this
  /// [RenderObjectWidget] represents, using the configuration described by this
  /// [RenderObjectWidget].
  ///
  /// This method should not do anything with the children of the render object.
  /// That should instead be handled by the method that overrides
  /// [RenderObjectElement.mount] in the object rendered by this object's
  /// [createElement] method. See, for example,
  /// [SingleChildRenderObjectElement.mount].
  @protected
  RenderObject createRenderObject(BuildContext context);
}
复制代码

什么是 RenderObject

RenderObject 用于应用界面的布局和绘制,保存了元素的大小,布局等信息,实例化一个 RenderObject 是很是耗能的。

当应用运行时 Flutter 使用 RenderObject 的数据绘制应用界面,最终造成一个 Render Tree

abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin implements HitTestTarget {
  /// Initializes internal fields for subclasses.
  RenderObject() {
    _needsCompositing = isRepaintBoundary || alwaysNeedsCompositing;
  }

  /// The render object at (or below) this location in the tree.
  ///
  /// If this object is a [RenderObjectElement], the render object is the one at
  /// this location in the tree. Otherwise, this getter will walk down the tree
  /// until it finds a [RenderObjectElement].
  RenderObject get renderObject {
    RenderObject result;
    void visit(Element element) {
      assert(result == null); // this verifies that there's only one child
      if (element is RenderObjectElement)
        result = element.renderObject;
      else
        element.visitChildren(visit);
    }
    visit(this);
    return result;
  }

  void layout(Constraints constraints, { bool parentUsesSize = false }) {
    /// ...
  }

  /// ...

  void paint(PaintingContext context, Offset offset) {
    /// ...
  }

}
复制代码

为何须要三棵树

使用三棵树的目的是尽量复用 Element

复用 Element 对性能很是重要,由于 Element 拥有两份关键数据:Stateful widget 的状态对象及底层的 RenderObject

当应用的结构很简单时,或许体现不出这种优点,一旦应用复杂起来,构成页面的元素愈来愈多,从新建立 3 棵树的代价是很高的,因此须要最小化更新操做。

当 Flutter 可以复用 Element 时,用户界面的逻辑状态信息是不变的,而且能够重用以前计算的布局信息,避免遍历整棵树。

举个例子说明

建立一个简单的 Flutter 应用,代码以下

import 'package:flutter/material.dart';

void main() {
  runApp(
    MaterialApp(
      color: Colors.white,
      debugShowCheckedModeBanner: false,
      builder: (context, child) => HomePage(),
    ),
  );
}

class HomePage extends StatefulWidget {
  @override
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  bool _isWorld = true;

  Widget _buildWorld() {
    return RichText(
      text: TextSpan(
        text: 'Hello world',
        style: TextStyle(color: Colors.black),
      ),
    );
  }

  Widget _buildFlutter() {
    return RichText(
      text: TextSpan(
        text: 'Hello flutter',
        style: TextStyle(color: Colors.black),
      ),
    );
  }

  void changeText() {
    setState(() {
      _isWorld = !_isWorld;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          Center(
            child: _isWorld ? _buildWorld() : _buildFlutter(),
          ),
          SizedBox(height: 20.0),
          // Padding(padding: EdgeInsets.only(top: 20.0)),
          IconButton(icon: Icon(Icons.refresh), onPressed: changeText)
        ],
      ),
    );
  }
}

复制代码

显示效果

simulator-world

打开 Dart DevTools,能够看到应用的 Widget Tree,此时 RichText 控件的 RenderObject 的 ID 是 #6276a

world-id

点击图标将文字变成 Hello flutter

simulator-flutter

刷新浏览器页面再次查看 RichTextRenderObject 的 ID 依然是 #6276a

flutter-id

能够发现 Flutter 只是更新了文字数据,复用了 RichText 对应的 ElementRenderObject

而使用 SizedBox 部件取代 Padding 部件时。

@override
Widget build(BuildContext context) {
  return Scaffold(
    body: Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: <Widget>[
        Center(
          child: RichText(
            text: TextSpan(
              text: 'Hello $text',
              style: TextStyle(color: Colors.black),
            ),
          ),
        ),
        SizedBox(height: 20.0),
        // Padding(padding: EdgeInsets.only(top: 20.0)),
        IconButton(icon: Icon(Icons.refresh), onPressed: changeText)
      ],
    ),
  );
}
复制代码

padding

Padding 部件对应的 ElementRenderObject 都会被从树中移除,使用 SizedBox 新生成的替代。

sizeedbox

总结

Widget 是应用界面的声明信息。 Element 连接 WidgetRenderObject,管理界面的更新和修改。 RenderObject 保存具体的布局信息,负责绘制 UI。

widget-element-render-object

参考

How Flutter renders Widgets (Video)

How Flutter renders Widgets

Flutter UI系统

博客地址

相关文章
相关标签/搜索