Flutter核心原理之Element与BuildContext

一,前言  

其实,Element与BuildContext之间的关系我们是可以通过源码分析的。

二,Element:

最终的UI树其实是由一个个独立的Element节点构成。组件最终的Layout、渲染都是通过RenderObject来完成的。

从创建到渲染的大体流程是:
根据Widget生成Element,然后创建相应的RenderObject并关联到Element.renderObject属性上,最后再通过RenderObject来完成布局排列和绘制。

Element就是Widget在UI树具体位置的一个实例化对象,大多数Element只有唯一的renderObject,但还有一些Element会有多个子节点,如继承自RenderObjectElement的一些类,比如MultiChildRenderObjectElement。最终所有Element的RenderObject构成一棵树,我们称之为渲染树,即render tree

  • Element的生命周期如下

     

    • 1. Framework 调用Widget.createElement 创建一个Element实例,记为element

    • 2. Framework 调用 element.mount(parentElement,newSlot)mount方法中首先调用elment所对应Widget的createRenderObject方法创建与element相关联的RenderObject对象,然后调用element.attachRenderObject方法将element.renderObject添加到渲染树中插槽指定的位置. 【这一步不是必须的,一般发生在Element树结构发生变化时才需要重新attach】插入到渲染树后的element就处于“active”状态,处于“active”状态后就可以显示在屏幕上了(可以隐藏)。

       

    • 3.当element父Widget的配置数据改变时,为了进行Element复用,Framework在决定重新创建Element前会先尝试复用相同位置旧的element:调用Element对应Widget的canUpdate()方法,如果返回true,则复用旧Element,旧的Element会使用新的Widget配置数据更新,反之则会创建一个新的Element,不会复用。Widget.canUpdate()主要是判断newWidget与oldWidget的runtimeType和key是否同时相等,如果同时相等就返回true,否则就会返回false。根据这个原理,当我们需要强制更新一个Widget时,可以通过指定不同的Key来禁止复用。【这就是我们常见的key的作用】

       

    • 4.当有父Widget的配置数据改变时,同时其State.build返回的Widget结构与之前不同,此时就需要重新构建对应的Element树。为了进行Element复用,在Element重新构建前会先尝试是否可以复用旧树上相同位置的element,element节点在更新前都会调用其对应Widget的canUpdate方法,如果返回true,则复用旧Element,旧的Element会使用新Widget配置数据更新,反之则会创建一个新的Element。Widget.canUpdate主要是判断newWidget与oldWidget的runtimeType和key是否同时相等,如果同时相等就返回true,否则就会返回false。根据这个原理,当我们需要强制更新一个Widget时,可以通过指定不同的Key来避免复用。

       

    • 5.当有祖先Element决定要移除element 时(如Widget树结构发生了变化,导致element对应的Widget被移除),这时该祖先Element就会调用deactivateChild 方法来移除它,移除后element.renderObject也会被从渲染树中移除,然后Framework会调用element.deactivate 方法,这时element状态变为“inactive”状态。

       

    • 6.“inactive”态的element将不会再显示到屏幕。为了避免在一次动画执行过程中反复创建、移除某个特定element,“inactive”态的element在当前动画最后一帧结束前都会保留,如果在动画执行结束后它还未能重新变成”active“状态,Framework就会调用其unmount方法将其彻底移除,这时element的状态为defunct,它将永远不会再被插入到树中。

       

    • 7.如果element要重新插入到Element树的其它位置,如element或element的祖先拥有一个GlobalKey(用于全局复用元素),那么Framework会先将element从现有位置移除,然后再调用其activate方法,并将其renderObject重新attach到渲染树。

  • 需要明白的一点是
      我们大多数情况下,只需要关注Widget树就行,Flutter框架已经将对Widget树的操作映射到Element树上,这可以极大的降低复杂度,提高开发效率。但是了解Element对理解整个Flutter UI框架是至关重要的,Flutter正是通过Element这个纽带将WidgetRenderObject关联起来,了解Element层不仅会帮助我们对Flutter UI框架有个清晰的认识,而且也会提高自己的抽象能力和设计能力。另外在有些时候,我们必须得直接使用Element对象来完成一些操作,比如获取主题Theme数据。 


三,BuildContext

  • BuildContext: 合理使用它,了解底层原理才会解决BUG。

在编写Flutter代码的时候,我们常常会调用BuildContext context这种,匿名函数中也出现着这样。

       

BuildContext其是一个抽象接口类。

  • 问题: StatelessWidgetStatefulWidgetbuild方法传入的context对象是哪个实现了BuildContext的类?

    进入到StatelessElement中的源码,可以发现如下:

 

     build传递的是this,很明显了,这个BuildContext很可能就是Element类,查看Element类定义,发现Element类果然实现了BuildContext接口:

 

BuildContext就是Widget对应的Element,所以我们可以通过contextStatelessWidgetStatefulWidget的build方法中直接访问Element对象。我们获取主题数据的代码Theme.of(context)内部正是调用了ElementinheritFromWidgetOfExactType()方法。

四,扩展

Widget层有时候并不能完全屏蔽Element细节,所以Framework在StatelessWidget和StatefulWidget中通过build方法参数将Element对象也传递给了开发者,这样便可以在需要时直接操作Element对象。

  1. 如果没有Widget层,单靠Element层是否可以搭建起一个可用的UI框架?如果可以应该是什么样子?
  2. Flutter UI框架能不做成响应式吗?
  • 1.肯定的,因为我们之前说过Widget树只是Element树的映射,我们完全可以直接通过Element来搭建一个UI框架.
    • 官方代码示例:
      class HomeView extends ComponentElement{
         HomeView(Widget widget) : super(widget);
         String text = "123456789";
      
         @override
         Widget build() {
           Color primary=Theme.of(this).primaryColor; //1
           return GestureDetector(
             child: Center(
              child: FlatButton(
               child: Text(text, style: TextStyle(color: primary),),
               onPressed: () {
                 var t = text.split("")..shuffle();
                 text = t.join();
                 markNeedsBuild(); //点击后将该Element标记为dirty,Element将会rebuild
               },
              ),
             ),
            );
         }
      }
      

      上面build方法不接收参数,这一点和在StatelessWidget和StatefulWidget中build(BuildContext)方法不同。代码中需要用到BuildContext的地方直接用this代替即可,如代码注释1处Theme.of(this)参数直接传this即可,因为当前对象本身就是Element实例。

      当text发生改变时,我们调用markNeedsBuild()方法将当前Element标记为dirty即可,标记为dirtyElement会在下一帧中重建。实际上,State.setState()在内部也是调用的markNeedsBuild()方法。

      上面代码中build方法返回的仍然是一个Widget,这是由于Flutter框架中已经有了Widget这一层,并且组件库都已经是以Widget的形式提供了,如果在Flutter框架中所有组件都像示例的HomeView一样以Element形式提供,那么就可以用纯Element来构建UI了,HomeView的build方法返回值类型就可以是Element了。

      需要将上面代码在现有Flutter框架中跑起来,那么还是得提供一个”适配器“WidgetHomeView结合到现有框架中,下面CustomHome就相当于”适配器“:

      class CustomHome extends Widget {
        @override
        Element createElement() {
         return HomeView(this);
        }
      }
  • 2.Flutter engine提供的dart API是原始且独立的,这个与操作系统提供的API类似,上层UI框架设计成什么样完全取决于设计者,完全可以将UI框架设计成Android风格或iOS风格,但这些事Google不会再去做,所以开发者也没必要再去搞这一套,这是因为响应式的思想本身是很棒的。


原文:https://blog.csdn.net/qq_39969226/article/details/94211852

posted on 2019-07-12 10:54  梁飞宇  阅读(2459)  评论(0编辑  收藏  举报