美文网首页
flutter-Widget Element Rende

flutter-Widget Element Rende

作者: 浮华_du | 来源:发表于2021-04-12 17:28 被阅读0次

    flutter的理念是: 一切都是Widget(Everything is Widget)
    我们在开发Flutter app的时候主要都是在写很多Widget。但是,Widget并不是我们认为的view,因为它并不是真正的渲染对象 。事实上在 Flutter 中渲染是经历了这样的过程:

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

    首先, 简单描述一下三者的关系:

    • Widget 只是 Element 的一个配置描述 ,告诉 Element 这个实例如何去渲染。
    • Widget 和 Element 之间是一对多的关系 。这是因为同一个Widget对象可以被添加到UI树的不同部分,而真正渲染时,UI树的每个Element节点都会对应一个Widget对象 (实际上渲染树是由 Element 实例的节点构成的树,而作为配置文件的 Widget 可能被复用到树的多个部分,对应产生多个 Element 对象)。
    • RenderObject源码注释写着 An object in the render tree 可以看出到 RenderObject 才是实际的渲染对象,而通过 Element 源码我们可以看出:Element 持有 RenderObject 和 Widget

    (其实,查看源码可知 Element分为三类:RenderObjectElement、ComponentElement、_NullElement,仅当子类是RenderObjectElement时,才具备RenderObject.本文涉及到的element的讲述,仅仅是RenderObjectElement)

    所以,element作为widget和renderObject的桥梁,还是有必要研究一下的. 结合element生命周期状态,我们来看一下element的工作:

    enum _ElementLifecycle {
      initial,
      active,
      inactive,
      defunct,
    }
    

    1. Framework 调用Widget.createElement 创建一个Element实例;完成了createElement后Element的状态为initial。

    • 创建element的时候需要传入一个widget,这样element中就有了Widget的实例
    framework调用Widget.createElement.png Element的构造方法.png

    widget中需要提供创建element的方法


    StatelessWidget实现createElement方法.png
    StatefuleWidget实现createElement方法.png

    2.Framework 调用 element.mount(parentElement,newSlot); 之后,Element的状态已经被改为active。

    element中的mount方法.png RenderObjectElement中的mount方法.png
    RenderObjectWidget中的createRenderObject方法.png
    image.png
    • Elment本身的mount方法只有生命周期的改变;在RenderObjectElement的mount方法中,首先调用elment所对应Widget的createRenderObject方法创建与element相关联的RenderObject对象;(这时候,element就有自己对应的renderObject了;并且,由以上代码可见, createRenderObject方法中的BuildContext 即为 element.)

      element.attachRenderObject方法.png
    • 因为Element本身不进行layout,所以Elment本身的mount方法只有生命周期的改变,那么layout是在哪里进行的呢?打开RenderObjectElement的源码,看到mount方法,通过调用attachRenderObject方法将render object 添加到 render tree中,在此,实现了Element的Layout。

    element.attachRenderObject方法将element.renderObject添加到渲染树中插槽指定的位置. 【这一步不是必须的,一般发生在Element树结构发生变化时才需要重新attach】插入到渲染树后的element就处于“active”状态,处于“active”状态后就可以显示在屏幕上了(可以隐藏)。


    image.png

    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的作用】


      image.png
      image.png
      image.png

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

    image.png

    5.“inactive”状态的element将不会再显示到屏幕。

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


      image.png

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

    image.png

    7.BuildContext即element

    image.png image.png
    image.png
    image.png

    例子

    import 'package:flutter/material.dart';
    
    class ElementTestPage extends StatelessWidget {
      const ElementTestPage({Key key}) : super(key: key);
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(),
          body: MyWidget(),
        );
      }
    }
    
    class MyWidget extends Widget {
      @override
      MyElement createElement() => MyElement(this);
    
      ///完成了createElement后Element的状态为initial。
    }
    
    class MyElement extends ComponentElement {
      int _counter = 0;
    
      MyElement(Widget widget) : super(widget);
    
      @override
      void mount(Element parent, dynamic newSlot) {
        print('当前element生命周期_ElementLifecycle.initial');
        super.mount(parent, newSlot);
        print('触发mount生命周期===当前element生命周期_ElementLifecycle.active');
      }
    
      @override
      void deactivate() {
        print('当前element生命周期_ElementLifecycle.active');
        super.deactivate();
        print('触发deactivate生命周期===当前element生命周期_ElementLifecycle.inactive');
      }
    
      @override
      void unmount() {
        print('当前element生命周期_ElementLifecycle.inactive');
        super.unmount();
        print('触发unmount生命周期===当前element生命周期_ElementLifecycle.defunct');
      }
    
      @override
      Widget build() {
        print("触发build");
        return Column(
          children: [
            Text('我只是一个显示控件$_counter'),
            FloatingActionButton(
              heroTag: "get_test1_increment",
              onPressed: () {
                _counter++;
                markNeedsBuild(); //点击后将该Element标记为dirty,Element将会rebuild
              },
              child: Icon(Icons.add),
            )
          ],
        );
      }
    }
    
    

    查看源码可知, setState里面调用的也是:
    _element!.markNeedsBuild();

    上述,我们知道BuildContext即widget对应的element, 利用element. markNeedsBuild()可以让widget重新执行build方法. 所以,我们想 在statelesswidget里面 如果想改变widget实现重新build的话,是不是也可以呢? 答案是可以的.(代码如下)
    (不过,如果我们通过Statelesswidget的element.markNeedsBuild() 直接重新build的话,BuildContext只是在build方法内存在,每次都会更新,且无法设置成成员变量,操作复杂. 所以不建议这么操作)

    class ElementTestPage extends StatelessWidget {
      const ElementTestPage({Key key}) : super(key: key);
    
      @override
      Widget build(BuildContext context) {
        print("context is Element----${context is Element}");
        Element element = context as Element;
        return Scaffold(
          appBar: AppBar(),
          body: Column(
            children: [
              Text('我只是一个显示控件'),
              FloatingActionButton(
                heroTag: "get_test1_increment",
                onPressed: () {
                  element.markNeedsBuild(); //点击后将该Element标记为dirty,Element将会rebuild
                },
                child: Icon(Icons.change_history),
              )
            ],
          ),
        );
      }
    }
    
    

    8.看一下源码中Element的描述,简单的翻译一下

    /// An instantiation of a [Widget] at a particular location in the tree.
    

    (element是树中特定位置的[Widget]实例)

    ///   
    /// Widgets describe how to configure a subtree but the same widget can be used 
    /// to configure multiple subtrees simultaneously because widgets are immutable.
    

    (widget描述如何配置子树,但是同一个widget可以用于同时配置多个子树,因为widget是不可变的。)

    /// An [Element] represents the use of a widget to configure a specific location
    /// in the tree. Over time, the widget associated with a given element can
    /// change, for example, if the parent widget rebuilds and creates a new widget
    /// for this location.
    

    ([Element]表示使用widget来配置树中的特定位置。随着时间的推移,给定元素关联的widget可能会发生变化,例如,如果父widget为该位置重建并创建新的widget。)

    /// Elements form a tree. Most elements have a unique child, but some widgets
    /// (e.g., subclasses of [RenderObjectElement]) can have multiple children.
    

    (element树, 大多数element都有唯一的子元素,但是一些widget(例如,[RenderObjectElement]的子类)可以有多个子元素。)

    /// Elements have the following lifecycle:
    

    (element有以下生命周期:)

    ///  * The framework creates an element by calling [Widget.createElement] on the
    ///    widget that will be used as the element's initial configuration.
    
    1. (framework调用[Widget.createElement]创建一个element,用于初始配置widget的build方法。)
    ///  * The framework calls [mount] to add the newly created element to the tree
    ///    at a given slot in a given parent. 
    
    1. (framework调用[mount]方法,将新创建的element添加到树中给定父级中的给定插槽中。)
    ///The [mount] method is responsible for
    ///    inflating any child widgets and calling [attachRenderObject] as
    ///    necessary to attach any associated render objects to the render tree.
    
    • ([mount]方法负责渲染任何子widget并根据需要调用[attachRenderObject],以将任何关联的渲染对象renderObject附加到渲染树render tree.)
    ///  * At this point, the element is considered "active" and might appear on
    ///    screen.
    

    (此时,元素状态是active,可能会出现在屏幕上。)

    ///  * At some point, the parent might decide to change the widget used to
    ///    configure this element, for example because the parent rebuilt with new
    ///    state.
    
    1. (在某个时刻,父级可能会决定更改用于配置此元素的widget,例如,因为父级使用新Sate重建。)
    /// When this happens, the framework will call [update] with the new
    ///    widget.
    

    (当这种情况发生时,framework将用新的小部件调用[update]。)

    The new widget will always have the same [runtimeType] and key as
    ///    old widget. If the parent wishes to change the [runtimeType] or key of
    ///    the widget at this location in the tree, it can do so by unmounting this
    ///    element and inflating the new widget at this location.
    

    (新widget将始终具有与旧widget相同的[runtimeType]和key。如果父级希望在树中的这个位置更改widget的[runtimeType]或key,可以通过unmounting这个元素并在这个位置inflating新的widget来完成。)

    ///  * At some point, an ancestor might decide to remove this element (or an
    ///    intermediate ancestor) from the tree, which the ancestor does by calling
    ///    [deactivateChild] on itself. 
    
    1. (在某个时刻,祖先可能会决定从树中删除这个元素(或中间祖先),是通过对自己调用[deactivateChild]来完成的。)
    ///Deactivating the intermediate ancestor will
    ///    remove that element's render object from the render tree and add this
    ///    element to the [owner]'s list of inactive elements, causing the framework
    ///    to call [deactivate] on this element.
    

    (停用中间祖先将从呈现树中删除该element的render object,并将该element添加到[owner]的非活动元素列表( inactive elements)中,从而导致framework对此element调用[deactivate]。)

    ///  * At this point, the element is considered "inactive" and will not appear
    ///    on screen. An element can remain in the inactive state only until
    ///    the end of the current animation frame. At the end of the animation
    ///    frame, any elements that are still inactive will be unmounted.
    
    1. (此时,该元素被认为是“非活动”的,不会出现在屏幕上。元素只能在当前动画帧结束之前保持非活动状态。在动画帧的末尾,仍处于非活动状态的任何元素都将被unmount。)
    ///  * If the element gets reincorporated into the tree (e.g., because it or one
    ///    of its ancestors has a global key that is reused), the framework will
    ///    remove the element from the [owner]'s list of inactive elements, call
    ///    [activate] on the element, and reattach the element's render object to
    ///    the render tree. (At this point, the element is again considered "active"
    ///    and might appear on screen.)
    
    1. (如果元素被重新合并到树中(例如,因为它或它的一个祖先有一个可重用的全局键),框架将从[owner]的非活动元素列表中删除该元素,对该元素调用[activate],并将该元素的呈现对象重新附加到呈现树。(此时,元素再次被视为“活动”并可能出现在屏幕上。))
    ///  * If the element does not get reincorporated into the tree by the end of
    ///    the current animation frame, the framework will call [unmount] on the
    ///    element.
    

    (如果元素在当前动画帧结束时没有重新合并到树中,框架将对元素调用[unmount]。)

    ///  * At this point, the element is considered "defunct" and will not be
    ///    incorporated into the tree in the future.
    

    (在这一点上,元素被认为是“不存在的”,将来不会合并到树中。)

    https://www.cnblogs.com/lxlx1798/articles/11174779.html

    相关文章

      网友评论

          本文标题:flutter-Widget Element Rende

          本文链接:https://www.haomeiwen.com/subject/ujqghltx.html