温故知新-Widget、Element、RenderObject

66 阅读9分钟

Widget、Element、RenderObject之间的关系

Widget

Widget是对UI的一种描述,可以理解为UI的配置项,定义了UI的外观和行为。Widget的种类有很多,一般是按照组合类、渲染类等区分,但是在实际的使用中按照功能和使用场景区分,可能理解上会更方便。

1.内容展示

不管是什么平台,平时用的最多的无非是文本、图片、按钮这些。
因此我们需要熟练掌握的也就是Text、Text.rich、Image、Button、TextField等。
在使用TextField的时候,要注意安卓手机上焦点的问题(尤其页面上有多个TextField);还有就是其父组件是Column时,键盘的出现可能导致约束出问题;

2.元素排列

用于构建UI的基本布局结构,控制组件的排列方式,类似CSS。
这类Widget组件有Row、Column、Flex、Stack、Center、Padding、Container、Wrap

3.交互类

在构建一个功能时,不光只有展示,还有一些交互,比如滚动、点击、拖拽、输入等。
这类Widget组件有ListView、GridView、GestureDetector、InkWell、SingleChildScrollView、Draggable等。

4.系统类

对于App开发来说,我们经常面对的就是一个个页面,而页面是必不可少的有导航栏、路由等;
用到的基本就是Scaffold、AppBar,这类也都会根据公司的整体设计风格封装成业务组件。

5.约束类

约束类的组件主要对其子组件做一个尺寸上约束,这类组件有LayoutBuilder、LimitedBox、SizedBox、ConstrainedBox等,这几个组件在工作中使用较为频繁。

对于初学者来说,掌握上面这些Widget,已经可以实现95%的业务需求,一些特殊的交互和场景我们在遇见的时候再去查找学习就好了。在平时的开发中,还有系统提供的功能性组件,可以简化我们的工作,比如FutureBuilder、Opacity、Visibility、ValueListenableBuilder等。
但是有一种场景,就是需要我们提前知道文本占用的尺寸,然后再去排列子组件,这种情况处理起来就比较麻烦一些。我的方案是,给显示的Widget添加GlobalKey,利用帧调度的帧后回调去做一次布局计算,然后通过GlobalKey获取尺寸,再重新渲染一次,当然这需要一些变量去控制状态。

RenderObject

RenderObjectFlutter渲染系统的核心抽象类,不可被实例化,所有显示在屏幕上的UI都是RenderObject的子类,负责实现布局(layout)、绘制(paint)、命中测试(hit testing)和语义化(semantics)等底层渲染逻辑。它是渲染树(render tree)的基本节点,所有可见 Widget 最终都会通过RenderObjectWidget创建对应的RenderObject来完成实际渲染工作。
在实际开发的时候并不需要关心RenderObject子类的具体实现,这里做个TODO作者本人目前还没详细学习RenderObject

Element

正确的创建顺序应该是Widget->Element->RenderObject,作者是故意调整了一下顺序,一是因为RenderObject的详细内容还没有学习到,二是Element作为WidgetRenderObject的桥梁需要着重学习下(主要也刚好看到这里)。
Element是抽象类,实现了BuildContext中定义的方法。同时BuildContext也是一个抽象类,表示了Widget的上下文信息,由Element具体实现,在开发中我们碰到的context,实际就是Element的具体实现。

BuildContext

BuildContext中定义了字段的get方法、获取祖先Widget和State的方法、跨Widget获取共享数据的方法。

  1. context的生命周期与 Widget 一致:当 Widget 被从树中移除时,其对应的context会失效,此时使用该context可能导致崩溃(如异步操作中误用已销毁的context)。
  2. 避免在initState中直接使用contextinitState执行时,Widget 还未完全插入到树中,此时调用findAncestor...等方法可能返回null。如需使用,可通过WidgetsBinding.instance.addPostFrameCallback延迟到第一帧构建完成后获取。
  3. context是 “位置标识” :不同 Widget 的context不同,即使是同一类型的 Widget,位于树中不同位置时,context也不同(对应不同的Element)。

1755231250963.jpg

Element的实现
  • Element的生命周期
    1. Widget作为配置数据,通过调用Widget.createElement方法创建 element
    2. 调用 [mount] 方法将新创建的 element 添加到树中(父节点给定的插槽);[mount] 方法负责膨胀 widget,并在必要的时候调用 [attachRenderObject] 将关联的 render objects 关联到 渲染树上,此时element的状态是 “active”的,并且可能已经显示到屏幕上;
    3. 某些情况父级可能会变更子Element的Widget,例如,因为父级使用新状态重建,此时框架将使用新 Widget 调用 [update] 方法
    4. 某些情况父节点可能决定从树中移除 element,父节点通过调用自己的[deactivateChild]方法实现。此父节点将从 render tree 中移除 element's 的 render object,并将 element 添加到 [owner]inactive 列表,框架将会触发 element 调用 [deactivate]
    5. 此时,该 element 将处于“inactive” 状态,并且不会出现在屏幕上。直到当前帧动画结束前,element 一直保持 “inactive” 状态,在帧动画结束时,处于 "inactive"状态的 element 将会被卸载;
    6. 如果 element 被重新合并到树中(比如一或多个父节点通过 global key 复用了此 element),element 将会从[owner]inactive 列表中移除,并调用[activate]方法,并将 element 的 render object 添加到 render tree 中(此时,element 在此成为 “active" 状态,并可能出现在屏幕上)。如果 element 在当前帧动画结束时,没有重新加到树中,element 将会调用[unmount]。此时,element 的状态为 ”defunct“,并且不会在被加到树中。
  • 核心字段
   /// 通过给定的 Widget 作为配置项,创建 Element 对象 
   /// 通常通过覆盖[Widget.createElement]调用 
   Element(Widget widget): _widget = widget; 
   
   /// 父 Element,类型为`Element?`,用于维护 Element 树的层级关系。
   Element? _parent;
   
   /// Element 的配置信息 
   Widget? _widget; 
   @override Widget get widget => _widget!; 
   
   /// 是否完成挂载到Widget Tree中
   bool get mounted => _widget != null;
   
   /// 负责调度 Element 的重建、清理等全局操作;
   BuildOwner? _owner; 
   @override BuildOwner? get owner => _owner; 
   
  /// `depth`:当前 Element 在树中的深度(整数),用于确定渲染顺序(深度小的先渲染)。
  late int _depth;
   
   _ElementLifecycle _lifecycleState = _ElementLifecycle.initial; 
   
   /// 如果 Element 被标记需要 rebuilding,则该值为 true 
   bool _dirty = true; 
   
   /// 是否在owner._dirtyElements中。它用于确定在该元素重新激活时是否应该将其添加回列表。
   bool _inDirtyList = false; 
   
   /// BuildScope的dirty elements列表,只能由BuildOwner.buildScope调用重建,其参数就是标记为脏的element列表
   BuildScope get buildScope => _parentBuildScope!;
   BuildScope? _parentBuildScope;
  • getter方法
/// 获取当前位置上的RenderObject,
/// 如果当前节点不是RenderObjectElement,那么则沿着element tree往下找,直到找到或者没有子节点
 /// 可能返回null:element尚未mount(如initiateState时)、非渲染行的Widget、element已经被unmount
 /// RenderObect有什么作用呢?一般获取的RenderObject真正的类型是其子类RenderBox,可以用过它获取View的size和position
RenderObject? get renderObject {
    Element? current = this;
    while (current != null) {
      if (current._lifecycleState == _ElementLifecycle.defunct) {
        break;
      } else if (current is RenderObjectElement) {
        return current.renderObject;
      } else {
        current = current.renderObjectAttachingChild;
      }
    }
    return null;
  }
  
  Element? get renderObjectAttachingChild {
    Element? next;
    visitChildren((Element child) {
      next = child;
    });
    return next;
  }
  • InheritedWidget相关的方法(BuildContext的接口的实现)
    以下几个方法是获取共享数据、状态管理的核心方法,在使用Provider状态管理时,使用的原理就是这几个方法。
    InheritedWidget dependOnInheritedElement(
    InheritedElement ancestor, {
    Object? aspect,
  }) {
    _dependencies ??= HashSet<InheritedElement>();
    _dependencies!.add(ancestor);
    ancestor.updateDependencies(this, aspect);
    return ancestor.widget as InheritedWidget;
  }
  
   T? dependOnInheritedWidgetOfExactType<T extends InheritedWidget>({
    Object? aspect,
  }) {
    assert(_debugCheckStateIsActiveForAncestorLookup());
    final InheritedElement? ancestor = _inheritedElements?[T];
    if (ancestor != null) {
      return dependOnInheritedElement(ancestor, aspect: aspect) as T;
    }
    _hadUnsatisfiedDependencies = true;
    return null;
  }
  
  T? getInheritedWidgetOfExactType<T extends InheritedWidget>() {
    return getElementForInheritedWidgetOfExactType<T>()?.widget as T?;
  }
  
  InheritedElement? getElementForInheritedWidgetOfExactType<T extends InheritedWidget>() {
    assert(_debugCheckStateIsActiveForAncestorLookup());
    return _inheritedElements?[T];
  }
  • 查找最近祖先Widget或State
 T? findAncestorWidgetOfExactType<T extends Widget>() {
    assert(_debugCheckStateIsActiveForAncestorLookup());
    Element? ancestor = _parent;
    while (ancestor != null && ancestor.widget.runtimeType != T) {
      ancestor = ancestor._parent;
    }
    return ancestor?.widget as T?;
  }

  @override
  T? findAncestorStateOfType<T extends State<StatefulWidget>>() {
    assert(_debugCheckStateIsActiveForAncestorLookup());
    Element? ancestor = _parent;
    while (ancestor != null) {
      if (ancestor is StatefulElement && ancestor.state is T) {
        break;
      }
      ancestor = ancestor._parent;
    }
    final StatefulElement? statefulAncestor = ancestor as StatefulElement?;
    return statefulAncestor?.state as T?;
  }

  @override
  T? findRootAncestorStateOfType<T extends State<StatefulWidget>>() {
    assert(_debugCheckStateIsActiveForAncestorLookup());
    Element? ancestor = _parent;
    StatefulElement? statefulAncestor;
    while (ancestor != null) {
      if (ancestor is StatefulElement && ancestor.state is T) {
        statefulAncestor = ancestor;
      }
      ancestor = ancestor._parent;
    }
    return statefulAncestor?.state as T?;
  }

  @override
  T? findAncestorRenderObjectOfType<T extends RenderObject>() {
    assert(_debugCheckStateIsActiveForAncestorLookup());
    Element? ancestor = _parent;
    while (ancestor != null) {
      if (ancestor is RenderObjectElement && ancestor.renderObject is T) {
        return ancestor.renderObject as T;
      }
      ancestor = ancestor._parent;
    }
    return null;
  }
  
  void didChangeDependencies() {
   ......
    markNeedsBuild();
  }
  
  • Element生命周期核心方法
   /// 将element添加到给定的parent的slot中;
   /// _lifecycleState由initial变为active
   /// 
   void mount(Element? parent, Object? newSlot) {
    /// _lifecycleState == _ElementLifecycle.initial
    /// _parent == null
    /// slot == null
    /// parent == null || parent._lifecycleState == _ElementLifecycle.active
    _parent = parent;
    _slot = newSlot;
    _lifecycleState = _ElementLifecycle.active;
    _depth = 1 + (_parent?.depth ?? 0);
    if (parent != null) {
      _owner = parent.owner;
      _parentBuildScope = parent.buildScope;
    }

    final Key? key = widget.key;
    if (key is GlobalKey) {
      owner!._registerGlobalKey(key, this);
    }
    _updateInheritance();
    ......
  } 

updateChild是Widget系统的核心方法,每次根据更新后的配置添加、更新或移除子项时,都会调用此方法。

newWidget == nullnewWidget != null
child == nullReturns null.Returns new [Element].
child != nullOld child is removed, returns null.Old child updated if possible, returns child or new [Element].
Element? updateChild(Element? child, Widget? newWidget, Object? newSlot) {
    if (newWidget == null) {
      if (child != null) {
         /// 1.如果 newWidget 为 null,child 不为 null,则移除 child,因为此时 child 的 widget 为null(即没有配置信息)
        deactivateChild(child);
      }
      /// 2.如果都为 null,则什么都不做 
      /// 1和2 都返回 null
      return null;
    }

    final Element newChild;
    if (child != null) {
       bool hasSameSuperclass = true;
       assert(() {
        final int oldElementClass = Element._debugConcreteSubtype(child);
        final int newWidgetClass = Widget._debugConcreteSubtype(newWidget);
        hasSameSuperclass = oldElementClass == newWidgetClass;
        return true;
      }());
      if (hasSameSuperclass && child.widget == newWidget) {
        if (child.slot != newSlot) {
          
          updateSlotForChild(child, newSlot);
        }
        /// 3. 如果 child 不为 null,且 配置信息没有发生改变,则直接返回 child
        newChild = child;
      } else if (hasSameSuperclass &&
          Widget.canUpdate(child.widget, newWidget)) {
        /// 4. 如果 child 不为 null,且需要更新配置信息,则根据 newWidget 更新 child,然后返回 child
        if (child.slot != newSlot) {
          updateSlotForChild(child, newSlot);
        }
        child.update(newWidget);
        newChild = child;
      } else {
        /// 5. 
        ///    5.1 父类发生了改变 => stateful 和 statless之间切换 
        ///    5.2 配置信息发生了改变 
        ///    5.3 widget 的[runtimeType] 或 [key] 发生了改变 
        
        /// a. 将旧的 element 从树中移除 
        deactivateChild(child);
        /// b. 创建新的 element
        newChild = inflateWidget(newWidget, newSlot);
      }
    } else {
      /// 6. child 为 null,newWidgte 不为 null,则根据新的配置信息创建新的 child 返回
      newChild = inflateWidget(newWidget, newSlot);
    }
    return newChild;
  }

根据给定的widget创建一个element,并将其作为child添加到指定的slot中。此方法通常由updateChild调用。
如果给定的 widget 存在 GlobalKey,并且global存在对应的 element,则该函数将会复用 element,而不是创建一个新的。element可能是树中其他位置的移植的,或者是从inactive列表中取的。

 Element inflateWidget(Widget newWidget, Object? newSlot) {
    try {
      final Key? key = newWidget.key;
      if (key is GlobalKey) {
        final Element? newChild = _retakeInactiveElement(key, newWidget);
        if (newChild != null) {
          try {
            newChild._activateWithParent(this, newSlot);
          } catch (_) {
            try {
              deactivateChild(newChild);
            } catch (_) {
                ......
            }
            rethrow;
          }
          final Element? updatedChild = updateChild(
            newChild,
            newWidget,
            newSlot,
          );
          assert(newChild == updatedChild);
          return updatedChild!;
        }
      }
      final Element newChild = newWidget.createElement();
      newChild.mount(this, newSlot);
      assert(newChild._lifecycleState == _ElementLifecycle.active);
      return newChild;
    } finally {
      ......
    }
  }

void deactivateChild(Element child) {
    assert(child._parent == this);
    child._parent = null;
    child.detachRenderObject();
    owner!._inactiveElements.add(
      child,
    ); 
}

void deactivate() {
    if (_dependencies?.isNotEmpty ?? false) {
      for (final InheritedElement dependency in _dependencies!) {
        dependency.removeDependent(this);
      }
    }
    _inheritedElements = null;
    _lifecycleState = _ElementLifecycle.inactive;
}

void activate() {
    final bool hadDependencies =
        (_dependencies?.isNotEmpty ?? false) || _hadUnsatisfiedDependencies;
    _lifecycleState = _ElementLifecycle.active;
    _dependencies?.clear();
    _hadUnsatisfiedDependencies = false;
    _updateInheritance();
    attachNotificationTree();
    if (_dirty) {
      owner!.scheduleBuildFor(this);
    }
    if (hadDependencies) {
      didChangeDependencies();
    }
}

void unmount() {
    ......
    final Key? key = _widget?.key;
    if (key is GlobalKey) {
      owner!._unregisterGlobalKey(key, this);
    }
    _widget = null;
    _dependencies = null;
    _lifecycleState = _ElementLifecycle.defunct;
} 
......

关于Element生命周期的方法,其子类会对其做做出修改,也会增加一些自己的方法,比如ComponentElement和RenderObjectElement,可以自行前往查看。