flutter: 根视图、根元素与根渲染

更新日期: 2019-07-04阅读: 2.5k标签: 渲染

flutter如何建立的视图树(WidgetTree),元素树(ElementTree)及渲染树(RenderingTree),又是如何更新视图绘制视图? 这个问题太大,刚开始一切又都是陌生的,理解起来千头万绪,所以先搞清这些树的根结点的身份是非常必要的。毫无疑问,这些根节点的建立紧密的与初始化过程关联,而确定了这些根节点之后,遍历查找更新就相对清晰了,因为绘制视图无非也是对树的遍历查找更新操作。

这部分就已经从引擎层进入到了dart层,需要了解的更多的是框架相关的机制,引擎目前用不到了。

环境: flutter sdk v1.7.8+hotfix.4@stable

先不要被Element, RenderObjectElement, RenderObject, Widget,RenderObjectWidget诸多名称吓到。与安卓封装了显式的启动运行过程不同,flutter有一个明确的runApp, 这就是进行分析的方便入口。


语言机制

多继承

需要先了解一下语言层面的一个多继承机制。虽然这里用了多继承这个名词,但是需要明确dart语言在语法上还是单继承,也就是只能extends一个类,其它接口分别再以with串接。


关键字声明

与java不同,dart没有interface(准确的说是已移除)只有abstract,abstract的使用与java并无二致。没有了interface如何实现多接口对象的声明?dart用的是mixin关键字,所以就方便理解而言,把mixin当作interface, on当作extends(只针对mixin类)即可。与interface不同的是mixin声明的类是可以有方法实现体和成员对象的

class A extends B implements C, D, E {}
class B {}
interface C {}
interface D {}
interface E {}

dart等同于:

class A extends B with C, D, E {}
class B {}
mixin C {}
mixin D {}
mixin E {}


继承顺序

在以上例子中假如B,C,D都有doSomeThing方法

class A extends B with C, D {
  @override
  void doSomeThing() {
    print("A");
    super.doSomeThing();
  }
}

class B {
  @override
  void doSomeThing() {
    print("B");
  }
}

mixin C on B {
  @override
  void doSomeThing() {
    print("C");
    super.doSomeThing();
  }
}

mixin D on B {
  @override
  void doSomeThing() {
    print("D");
    super.doSomeThing();
  }
}

void main() {
  A().doSomeThing();
}

那么当执行A.doSomeThing后应该是哪个调用顺序?
直接给结论:以with声明的反顺序继承
那么问题来了:如果没有C on B会发生什么?
语言机制问题可参考这篇文章


串连调用

需要了解的第2个语法特性是串连调用,可以用..操作符串连调用类的成员方法:

class F {
  String str;
  String contact(String s) {
    return str + s;
  }

  void assign(String s) {
    str = s;
  }
}

void mai() {
  F f = F()..assign("hello")..contact(' world');
  print(f.str);
}

需要明确:用了..操作符之后调用返回的就是类对象实例,不再是方法的返回值。


初始化调用

有了以上基础(用到语言特性1: mixin WidgetsBinding on BindingBase, SchedulerBinding, GestureBinding, RendererBinding, SemanticsBinding)就可以理清runApp入口的调用序列:

runApp
  WidgetsFlutterBinding.ensureInitialized
    WidgetsFlutterBinding()
      BindingBase()
        WidgetsBinding.initInstances
          RendererBinding.initInstances
            SemanticsBinding.initInstances
              PaintingBinding.initInstances
                SchedulerBinding.initInstances
                  ServicesBinding.initInstances
                    GestureBinding.initInstances
                      BindingBase.initInstances

这里包含了大量的数据初始化,用到一个找一个。
再看整体序列(widgets/binding.dart:786, 用到语言特性2):

runApp
  WidgetsFlutterBinding.ensureInitialized
  WidgetsBinding.attachRootWidget
  WidgetsBinding.scheduleWarmUpFrame

MyApp实例被传给了WidgetsBinding.attachRootWidget方法,于是分析其调用序列:

runApp
  WidgetsBinding.attachRootWidget
    RenderObjectToWidgetAdapter()
    RenderObjectToWidgetAdapter.attachToRenderTree
      RenderObjectToWidgetAdapter.createElement
      RenderObjectToWidgetElement<RenderBox>.assignOwner
      BuildOwner.buildScope
      RenderObjectToWidgetElement<RenderBox>.mount

需要注意RenderObjectToWidgetAdapter 是一个RenderObjectWidget类型,它用构造函数child: rootWidget, 持有了外部传入的rootWidget作为它的子视图。
RenderObjectToWidgetAdapter.createElement创建的元素被赋值给了_renderViewElement,_renderViewElement被WidgetsBinding实例持有。


元素关联渲染

那根渲染又是何时创建的呢?继续看mount的调用序列:

RenderObjectToWidgetElement<RenderBox>.mount
  RootRenderObjectElement.mount
    RenderObjectElement.mount
      RenderObjectWidget.createRenderObject => RenderObjectToWidgetAdapter.createRenderObject

这里容易让人误导,调用createRenderObject的其实是RenderObjectElement持有的RenderObjectWidget, 而元素RenderObjectToWidgetElement正是RenderObjectToWidgetElement<T> createElement() => RenderObjectToWidgetElement<T>(this)(widgets/binding.dart:833)所创建,这里的this其实就是RenderObjectToWidgetAdapter,所以根渲染是RenderObjectWithChildMixin<T> createRenderObject(BuildContext context) => container;(widgets/bindings.836),可见根渲染不是在此时创建的,而是预先被赋值仅在此时返回的。


各种根节点

由此可见MyApp作为外部传入的rootWidget不是真正的根视图,真正的根视图其实是RenderObjectToWidgetAdapter, 它被RenderObjectToWidgetElement<RenderBox>持有(一个Element持有一个Widget), 而这个Element被全局WidgetsBinding实例持有,所以根元素为RenderObjectToWidgetElement<RenderBox>。

RenderObjectElement在mount的时机创建了一个RenderObject实例并持有,而RenderObjectToWidgetElement是RenderObjectElement的子类,创建的RenderObject具体类型为RenderObjectWithChildMixin<RenderBox>,所以它才是最终的根渲染。

有了rootElement就可以找到rootWidget和rootRenderObject, 元素树,视图树与渲染树由此建立起来。


根渲染创建

回到RenderObjectToWidgetAdapter调用构造函数的地方,传入的container是RenderingBinding的RenderView get renderView => _pipelineOwner.rootNode;(rendering/binding.dart:162, attachRootWidget是WidgetsBinding的方法,但 mixin WidgetsBinding on RendererBinding,所以可以引用到RenderingBinding的成员)。

那么rootRenderObject,也就是上面的RenderView, 作为RenderObjectWithChildMixin<RenderBox>的子类(class RenderView with RenderObjectWithChildMixin<RenderBox>),又是在什么时机创建的?跟踪下来正是在初始化调用中:

runApp
  WidgetsFlutterBinding.ensureInitialized
    WidgetsFlutterBinding()
      BindingBase()
        WidgetsBinding.initInstances
          RendererBinding.initInstances
            _pipelineOwner = PipelineOwner(
            RendererBinding.initRenderView
              renderView = RenderView()
                _pipelineOwner.rootNode = value;

也就是说WidgetBinding把RendererBinding(mixin WidgetBinding with RendererBinding)的renderView作为了根渲染,而它实际是_pipelineOwner.rootNode。

至此,我们便知道了所有节点遍历的起点。


链接: https://fly63.com/article/detial/4512

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部分也提醒了

点击更多...

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