flutter-Widget Element Rende

2021-04-12  本文已影响0人  浮华_du

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

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

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

(其实,查看源码可知 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。

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

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


image.png

3.当element父Widget的配置数据改变时,为了进行Element复用,Framework在决定重新创建Element前会先尝试复用相同位置旧的element

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

image.png

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

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.
///  * 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

上一篇下一篇

猜你喜欢

热点阅读