Flutter 是如何渲染的?

更新日期: 2020-03-11阅读: 2k标签: 渲染

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

当应用启动时 Flutter 会遍历并创建所有的 Widget 形成 Widget Tree ,同时与 Widget Tree 相对应,通过调用 Widget 上的 createElement() 方法创建每个 Element 对象,形成 Element Tree 。

最后调用 Element 的 createRenderObject() 方法创建每个渲染对象,形成一个 Render Tree 。

然后需要知道 Widget , Element 和 RenderObject 到底是啥以及它们是干什么的。


什么是 Widget

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

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

@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;
  }
}

Widget 的 canUpdate 方法通过比较新部件和旧部件的 runtimeType 和 key 属性是否相同来决定更新部件对应的 Element 。


什么是 Element

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

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


当 Widget 变化时,如果两个 Widget 的 runtimeType 和 key 属性相同的,那么新的 Element 会通过 Element.update() 更新旧的 Element ,否则旧的 Element 会被删除,新生成的 Element 插入到树中。

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)
        ],
      ),
    );
  }
}


可以发现 Flutter 只是更新了文字数据,复用了 RichText 对应的 Element 和 RenderObject 。

而使用 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 部件对应的 Element 和 RenderObject 都会被从树中移除,使用 SizedBox 新生成的替代。


总结

Widget 是应用界面的声明信息。

Element 链接 Widget 和 RenderObject ,管理界面的更新和修改。

RenderObject 保存具体的布局信息,负责绘制 UI。

原文 https://coldstone.fun/post/2020/04/03/how-flutter-render/

链接: https://www.fly63.com/article/detial/8598

vue中数据更新变化,而页面视图未渲染的解决方案

在使用vue的时候,我们都知道它是双向数据绑定的,但是在使用不熟的情况下,经常会遇到:data中的数据变化了,但是并没有触发页面渲染。下面就整理一些出现这种情况的场景以及解决办法。

服务端渲染和客户端渲染的对比

这里结合art-template模板引擎说明。首先了解下前端页面中如何使用art-template。当不需要对SEO友好的时候,推荐使用客户端渲染;当需要对 SEO友好的时候,推荐使用服务器端渲染

解决使用vue.js未渲染前代码显示问题

在使用vue的时候,偶然发现多次刷新或者网络加载缓慢的时候,会一瞬间出现设置的模板的情况。实在很影响美观,可以使用vue现成的指令来解决这个问题:v-cloak

在微信小程序中渲染html内容的实现

大部分Web应用的富文本内容都是以HTML字符串的形式存储的,通过HTML文档去展示HTML内容自然没有问题。但是,在微信小程序(下文简称为「小程序」)中,应当如何渲染这部分内容呢?

原来 CSS 与 JS 是这样阻塞 DOM 解析和渲染的

估计大家都听过,尽量将 CSS 放头部,JS 放底部,这样可以提高页面的性能。然而,为什么呢?大家有考虑过么?很长一段时间,我都是知其然而不知其所以然,强行背下来应付考核当然可以,但实际应用中必然一塌糊涂

Vue渲染数据理解以及Vue指令

原生JS改变页面数据,必须要获取页面节点,也即是进行DOM操作,jQuery之类的框架只是简化DOM操作的写法,实质并没有改变操作页面数据的底层原理,DOM操作影响性能(导致浏览器的重绘和回流),Vue是一个mvvm框架(库),大幅度减少了DOM操作

Web渲染那些事儿

在决定渲染方式时,需要测量和理解真正的瓶颈在哪里。静态渲染或服务器渲染在多数情况都比较适用,尤其是可交互性对JS依赖较低的场景。下面是一张便捷的信息图,显示了服务器到客户端的技术频谱:

vue从后台获取数据赋值给data,如何渲染更细视图

如果从服务端返回的数据量较少,或者只有几个字段,可以用vue的set方法,如果数据量较大,请直接看第二种情况。官网API是这样介绍的:Vue.set(target,key,value)

react 异步加载数据时的渲染问题

当数据需要异步加载时render获取不到数据可能会报一些错误,此时需要在render函数中加一个判断.行到render时,state对象的haveData为false, 所以此时页面展示 loading,当异步获取数据成功时

Vue.js中v-html渲染的dom添加scoped的样式

在vue.js中,要将一段字符串渲染成html,可以使用v-html指令。但是 官方文档 中的v-html部分也提醒了

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!