Flutter 101: 何为 Flutter Elements ?

简介: 0 基础学习 Flutter,第一百零一步:简单了解一下 Element 原理!

      小菜前段时间简单了解了一下 Widget 的相关知识,其中 Widgetimmutable 不可变的,而 Widget 是如何做到更新重绘的,这就离不开 ElementRenderObject;小菜简单了解一下 Element 的相关小知识;

Element

      ElementWidgetUI 树具体位置的一个实例化对象;UI ViewElement 层级结构中会构建一个真实的 Element Tree,是真正的视图树结构;Element 作为 WidgetRenderObject 之间的协调者,并根据 Widget 的变化来完成结点的增删改的操作;

源码分析

      Element 所涉及源码较长,小菜仅针对具体的方法和生命周期进行学习;

生命周期

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

      Element 的生命周期主要包括如下几分,分别是 initial 初始化,active 活跃状态,inactive 不活跃状态以及 defunct 失效状态;

针对方法

1. createElement
Element(Widget widget) : assert(widget != null), _widget = widget;

      创建一个使用指定 Widget 作为其配置的 Element;通过 Widget 调用 Widget.createElement 来创建 Element,作为 Element 的初始位置;

2. mount
@mustCallSuper
void mount(Element parent, dynamic newSlot) {
    ...
    _parent = parent;
    _slot = newSlot;
    _depth = _parent != null ? _parent.depth + 1 : 1;
    _active = true;
    if (parent != null) 
      _owner = parent.owner;
    if (widget.key is GlobalKey) {
      final GlobalKey key = widget.key;
      key._register(this);
    }
    _updateInheritance();
}

      mount() 会将新创建的 Element 添加到指定的父级 slot 插槽树中,通过调用 attachRenderObject 添加到渲染树上;

3. update
@protected
Element updateChild(Element child, Widget newWidget, dynamic newSlot) {
    if (newWidget == null) {
      if (child != null) deactivateChild(child);
      return null;
    }
    if (child != null) {
      if (child.widget == newWidget) {
        if (child.slot != newSlot) updateSlotForChild(child, newSlot);
        return child;
      }
      if (Widget.canUpdate(child.widget, newWidget)) {
        if (child.slot != newSlot) updateSlotForChild(child, newSlot);
        child.update(newWidget);
        return child;
      }
      deactivateChild(child);
      assert(child._parent == null);
    }
    return inflateWidget(newWidget, newSlot);
}

      updateChildElement 的核心方法,每当需要增加,修改,删除子 child 时都会调用;主要根据 Widget 的变化用于 Element 的更新,进而更新 UI 树;

newWidget == null newWidget != null
child == null Returns null. Returns new [Element].
child != null Old child is removed, returns null. Old child updated if possible, returns child or new [Element].
  1. 当更新后的 Widgetnull 时,对应的子节点已经移除,如果当前 child 不为 null,则直接 remove 掉;
  2. 当更新后的 Widget 不为 null 且当前 childnull 时,说明新 Widget 是新创建的,则 inflateWidget 创建子节点;
  3. 当更新后的 Widget 不为 null 且当前 child 也不为 null 该节点存在时,若 child.widget == newWidget 说明子节点前后未发生变化,若 child.slot != newSlot 说明子节点在兄弟结点间移动了位置,此时 updateSlotForChild 更新节点位置;否则直接返回子节点;
  4. 当更新后的 Widget 不为 null 且当前 child 也不为 null 该节点存在时,若 Widget.canUpdatetrue 说明可以用 newWidget 修改子节点,直接调用 update 更新即可;否则先将子节点移除再通过 newWidget 创建新的子节点;其中 canUpdate 主要是判断新旧 WidgetkeyruntimeType 是否一致;
4. deactivate
@protected
void deactivateChild(Element child) {
    child._parent = null;
    child.detachRenderObject();
    owner._inactiveElements.add(child); // this eventually calls child.deactivate()
}

      deactivateChild 将指定 Element 已到非活动 Element 列表中,并将渲染对象从渲染树中移除;该方法可以阻止 Element 成为其子类;

5. activate
@mustCallSuper
void activate() {
    final bool hadDependencies = (_dependencies != null && _dependencies.isNotEmpty) || _hadUnsatisfiedDependencies;
    _active = true;
    _dependencies?.clear();
    _hadUnsatisfiedDependencies = false;
    _updateInheritance();
    
    if (_dirty)
      owner.scheduleBuildFor(this);
    if (hadDependencies)
      didChangeDependencies();
}

      activate() 为将 Element 重新合并到树上时,框架会从 inactive 非活跃 Element 列表中删除该元素,且该元素调用 activate 并将 Element 的渲染对象添加到渲染树上;

6. unmount
@mustCallSuper
void unmount() {
    if (widget.key is GlobalKey) {
        final GlobalKey key = widget.key;
        key._unregister(this);
    }
}

      unmount() 为当框架永远不会重新激活时调用;为了避免在一次动画执行过程中反复创建,移除特定 Element 时,非活跃状态的 Element 都会在当前动画过程最后一帧先保留,如果到动画结束后还未变成活跃状态,则调用 unmount() 将该 Element 彻底移除;

对应关系

  1. Widget.createElementinitial 从无到有的初始化生命周期;
  2. mountinitial 初始化状态到 active 活跃状态到生命周期过渡;
  3. update 只有在 active 活跃状态时才会调用;
  4. deactivateactive 活跃状态到 inactive 非活跃状态生命周期过渡;
  5. activateinactive 非活跃状态到 active 活跃状态的生命周期过渡;
  6. unmountinactive 非活动状态到 defunct 失效状态生命周期的过渡;

子类 Element

      Element 主要有组合类 ComponentElement 和渲染类 RenderObjectElement 两个子类;

ComponentElement

      ComponentElement 为组合类 Element,主要包括如下 StatelessElement / StatefulElement / ProxyElement 子类;其中各 Element 都是与 Widget 对应的;

StatelessElement
class StatelessElement extends ComponentElement {
  StatelessElement(StatelessWidget widget) : super(widget);

  @override
  StatelessWidget get widget => super.widget;

  @override
  Widget build() => widget.build(this);

  @override
  void update(StatelessWidget newWidget) {
    super.update(newWidget);
    assert(widget == newWidget);
    _dirty = true;
    rebuild();
  }
}

      StatelessElement 相对比较简单,主要是重写 update() 在需要发生变更时 rebuild() 即可;

StatefulElement
@override
void update(StatefulWidget newWidget) {
    super.update(newWidget);
    final StatefulWidget oldWidget = _state._widget;
    _dirty = true;
    _state._widget = widget;
    try {
      _debugSetAllowIgnoredCallsToMarkNeedsBuild(true);
      final dynamic debugCheckForReturnedFuture = _state.didUpdateWidget(oldWidget) as dynamic;
    } finally {
      _debugSetAllowIgnoredCallsToMarkNeedsBuild(false);
    }
    rebuild();
}

      StatefulElement 是对应 StatefulWidget 的,包括完整的 Element 生命周期,其中在更新时会更新 Staterebuild()

ProxyElement
abstract class ProxyElement extends ComponentElement 
  ProxyElement(ProxyWidget widget) : super(widget);

  @override
  ProxyWidget get widget => super.widget;

  @override
  Widget build() => widget.child;

  @override
  void update(ProxyWidget newWidget) {
    final ProxyWidget oldWidget = widget;
    assert(widget != null);
    assert(widget != newWidget);
    super.update(newWidget);
    assert(widget == newWidget);
    updated(oldWidget);
    _dirty = true;
    rebuild();
  }

  @protected
  void updated(covariant ProxyWidget oldWidget) {
    notifyClients(oldWidget);
  }

  @protected
  void notifyClients(covariant ProxyWidget oldWidget);
}

      ProxyElement 作为一个抽象类,其子类是 ParentDataElementInheritedElement;当 Widget 更新时调用 update()notifyClients() 用于新旧 Widget 确实已改变时调用;

RenderObjectElement

      RenderObjectElement 为渲染类型 Element 对应的是 RenderObjectWidgetRenderObjectElement 作为抽象类也继承了 Element 所有的生命周期方法;

      大多数的 RenderObjectElement 都只对应一个 RenderObject 即只有一个子节点,例如 RootRenderObjectElement / SingleChildRenderObjectElement;但也有特殊的,如 LeafRenderObjectElement 子类没有子节点,以及 MultiChildRenderObjectElement 子类可以有多个子节;


      Element 作为 WidgetRenderObject 的协作者起到了承上启下的左右;小菜对会在下一篇简单学习 RenderObject;小菜对源码的理解还不够深入,如有错误,请多多指导!

来源:阿策小和尚

目录
相关文章
|
2月前
|
Android开发 iOS开发 容器
鸿蒙harmonyos next flutter混合开发之开发FFI plugin
鸿蒙harmonyos next flutter混合开发之开发FFI plugin
|
26天前
|
开发框架 Dart 前端开发
Flutter 是谷歌推出的一款高效跨平台移动应用开发框架,使用 Dart 语言,具备快速开发、跨平台支持、高性能、热重载及美观界面等特点。
Flutter 是谷歌推出的一款高效跨平台移动应用开发框架,使用 Dart 语言,具备快速开发、跨平台支持、高性能、热重载及美观界面等特点。本文从 Flutter 简介、特点、开发环境搭建、应用架构、组件详解、路由管理、状态管理、与原生代码交互、性能优化、应用发布与部署及未来趋势等方面,全面解析 Flutter 技术,助你掌握这一前沿开发工具。
56 8
|
26天前
|
存储 JavaScript 前端开发
在Flutter开发中,状态管理至关重要。随着应用复杂度的提升,有效管理状态成为挑战
在Flutter开发中,状态管理至关重要。随着应用复杂度的提升,有效管理状态成为挑战。本文介绍了几种常用的状态管理框架,如Provider和Redux,分析了它们的基本原理、优缺点及适用场景,并提供了选择框架的建议和使用实例,旨在帮助开发者提高开发效率和应用性能。
34 4
|
26天前
|
传感器 前端开发 Android开发
在 Flutter 开发中,插件开发与集成至关重要,它能扩展应用功能,满足复杂业务需求
在 Flutter 开发中,插件开发与集成至关重要,它能扩展应用功能,满足复杂业务需求。本文深入探讨了插件开发的基本概念、流程、集成方法、常见类型及开发实例,如相机插件的开发步骤,同时强调了版本兼容性、性能优化等注意事项,并展望了插件开发的未来趋势。
39 2
|
6月前
|
开发框架 前端开发 测试技术
Flutter开发常见问题解答
Flutter开发常见问题解答
|
2月前
|
开发者
鸿蒙Flutter实战:07-混合开发
鸿蒙Flutter混合开发支持两种模式:1) 基于har包,便于主项目开发者无需关心Flutter细节,但不支持热重载;2) 基于源码依赖,利于代码维护与热重载,需配置Flutter环境。项目结构包括AppScope、flutter_module等目录,适用于不同开发需求。
103 3
|
1月前
|
传感器 开发框架 物联网
鸿蒙next选择 Flutter 开发跨平台应用的原因
鸿蒙(HarmonyOS)是华为推出的一款旨在实现多设备无缝连接的操作系统。为了实现这一目标,鸿蒙选择了 Flutter 作为主要的跨平台应用开发框架。Flutter 的跨平台能力、高性能、丰富的生态支持和与鸿蒙系统的良好兼容性,使其成为理想的选择。通过 Flutter,开发者可以高效地构建和部署多平台应用,推动鸿蒙生态的快速发展。
228 0
|
1月前
|
Dart 安全 UED
Flutter&鸿蒙next中的表单封装:提升开发效率与用户体验
在移动应用开发中,表单是用户与应用交互的重要界面。本文介绍了如何在Flutter中封装表单,以提升开发效率和用户体验。通过代码复用、集中管理和一致性的优势,封装表单组件可以简化开发流程。文章详细讲解了Flutter表单的基础、封装方法和表单验证技巧,帮助开发者构建健壮且用户友好的应用。
78 0
|
2月前
|
开发框架 移动开发 Android开发
安卓与iOS开发中的跨平台解决方案:Flutter入门
【9月更文挑战第30天】在移动应用开发的广阔舞台上,安卓和iOS两大操作系统各自占据半壁江山。开发者们常常面临着选择:是专注于单一平台深耕细作,还是寻找一种能够横跨两大系统的开发方案?Flutter,作为一种新兴的跨平台UI工具包,正以其现代、响应式的特点赢得开发者的青睐。本文将带你一探究竟,从Flutter的基础概念到实战应用,深入浅出地介绍这一技术的魅力所在。
91 7
|
2月前
|
编解码 Dart API
鸿蒙Flutter实战:06-使用ArkTs开发Flutter鸿蒙插件
本文介绍了如何开发一个 Flutter 鸿蒙插件,实现 Flutter 与鸿蒙的混合开发及双端消息通信。通过定义 `MethodChannel` 实现 Flutter 侧的 token 存取方法,并在鸿蒙侧编写 `EntryAbility` 和 `ForestPlugin`,使用鸿蒙的首选项 API 完成数据的读写操作。文章还提供了注意事项和参考资料,帮助开发者更好地理解和实现这一过程。
112 0