每日一题 Flutter#3 | 说说 Widget 的派生体系

最近在着手开发我的 《匠心星问》 ,它定位是一款 题库 应用,将集题目浏览、发布、解答、做题为一体。打算第一步先以 Flutter 为核心,准备题库资源。于是诞生《每日一题》 系列,准备精心设计一些 Flutter 的问题与解答,作为题库的养料。本文的焦点是探讨:

说说 Widget 的派生体系

Widget 是开发者搭建房子的材料,是 UI 界面搭建至关重要的因素。 Flutter 框架中有着丰富的组件可供开发者使用,对 Widget 派生体系的理解,能够站在更高的角度,审视整个构建系统,避免陷入 Widget 的泥沼中无法自拔。

- -

1. Widget 的派生体系概览

Widget 作为构建用户界面的基本单元,其派生体系不仅奠定了框架的声明式 UI 基础,更通过巧妙的层次划分实现了高效渲染与灵活扩展。

Widget 派生体系最直观的表现形式,就是 类的继承体系 。(如下图所示)去除掉 _ 相关的私有组件,就可以得到一幅派生体系的蓝图:

  • RootWidget: 应用程序的根组件。
  • PreferredSizeWidget: 向父组件提供首选尺寸。
  • StatefulWidget: 可变状态组件。
  • StatelessWidget: 不可变状态组件。
  • ProxyWidget : 代理它的子组件,本身不负责 UI 构建,常用于数据共享和功能注入。
  • RenderObjectWidget: 用于创建和配置底层渲染对象。

2.组合型: StatelessWidget 和 StatefulWidget

先从我们最最熟悉的 StatelessWidget 和 StatefulWidget 来说。Flutter 框架中估计有 500+ 的组件,其中 大约 80% 是 StatelessWidget 或 StatefulWidget。

之所以称它们是 组合型 Widget ,是因为它们会通过 build 方法返回 Widget。而这些返回的 Widget 通常是由多个子组件 组合 而成的 UI 树结构。也就是说,它们本身并不直接控制布局或渲染,而是作为 UI 描述器,通过组合其他 Widget 来构建特定的视图元件。

StatelessWidget ------ 无状态组件

StatelessWidget 表示无状态的组件,其 UI 完全由构造函数传入的参数决定。它不能 主动发生变化。如下所示中,只要 counter 不变, 每次构建出来的 UI 都是一样的。但外界可以通过创建不同参数的 CounterText,来让界面展示不同的文字:

dart 复制代码
class CounterText extends StatelessWidget {
  final int counter;

  const HelloText({required this.name});

  @override
  Widget build(BuildContext context) {
    return Text('Click $counter Times');
  }
}

StatefulWidget ------ 可变状态组件

相比之下,StatefulWidget 支持在生命周期中 维护状态并动态更新 UI 。常见的交互行为如点击计数、表单录入、动画表现等都依赖它来实现。它由两部分组成:

  • 【1】 StatefulWidget 本身:负责持有配置数据,不负责组件构建;
  • 【2】 State 类 :由 StatefulWidget 创建,持有对应的组件对象。负责维护状态数据、 build 方法构建组件。

组件最重要的是构建视图元件,而 StatefulWidgetState 像是一种是 代理关系StatefulWidget 将 UI 构建和状态管理责任的委托给 State 类处理,自身只是一个提供配置参数数据的 外壳

如下所示 ChangeableCounter 持有 Color 配置数据;而组件的构建逻辑交由 _ChangeableCounterState#build 来处理。State 中可以通过访问 widget 属性得到配置参数,从而可以对构建逻辑产生影响。比如这里通过组件中的 color 作为文字的颜色。
_ChangeableCounterState 不仅负责组件的构建,还要维护 _counter 状态数据,并在按钮点击时修改状态数据,并通过 setState 触发重新构建,来让视图发生变化。

dart 复制代码
class ChangeableCounter extends StatefulWidget {
  final Color color;

  const ChangeableCounter({super.key, required this.color});
  
  @override
  State createState() => _ChangeableCounterState();
}

class _ChangeableCounterState extends State<ChangeableCounter> {
  int _counter = 0;

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Text('Click $counter Times' ,style: TextStyle(color: widget.color)),
        ElevatedButton(
          onPressed: () => setState(() => _counter++),
          child: Text('Add'),
        ),
      ],
    );
  }
}

每当 StatefulWidget 对应的元素 (Element) 被创建之后,框架会调用其 createState() 方法生成一个 State 实例。之后,整个生命周期内,UI 的构建、状态的更新、生命周期的监听等,都是通过 State 这个"代理类"来完成的。


2. 劳苦功高的 RenderObjectWidget

StatelessWidget 和 StatefulWidget 是通过在 build 中组装 已有组件 来封装新的组件。打个比方,组合型 Widget 是将已有的人重新组合,形成解决特定问题的团队。团队的优势是 "调度",而不是"干活",那组成团队的人最终从哪里来呢? 真正的打工人就是 ---- RenderObjectWidget。

RenderObjectWidget 是 Flutter 渲染体系的桥梁。它不会直接参与 UI 构建,而是负责创建和配置底层的 RenderObject,而 RenderObject 是真正完成底层绘制、布局、事件处理等。

比如我们耳熟能详的 FlexWrapPaddingAlignCenterDecoratedBox 等和布局、渲染直接相关的组件都是 RenderObjectWidget 体系下的,它们一般都有着特定的功能。


可以把 RenderObjectWidget 理解为一线工程师 ,它们直接操控画布、计算布局、参与命中测试。而 StatelessWidgetStatefulWidget 更像是项目经理,负责调用这些工程师,组织资源,整合接口,让界面层协同工作。

几个简单的例子,Container 是一个 StatelessWidget ,它内部的构建逻辑中整合了如下七个基本功能的 RenderObjectWidget :

其价值在于可以将多个单子组件的树形组合,简化为一个组件。在语义方面更直观、简练(如下左图):


3. 数据传输公路 ProxyWidget

如果说 RenderObjectWidget 是 UI 的"施工者",StatelessWidgetStatefulWidget 是"组织者",那 ProxyWidget 则可以被称为 Flutter 中的数据传输公路 ------ 它自己不参与 UI 构建、不负责渲染,却在 Widget 树中扮演着信息中继、功能注入的重要角色。那什么是 ProxyWidget,它究竟代理了个啥?

从派生体系中可以看出,ProxyWidget 派生类大名鼎鼎的 InheritedWidget , 以及非常实用的 ParentDataWidget

  • InheritedWidget 的特点是持有数据,可以向子树传递。子节点通过上下文访问可以建立订阅关系。
  • ParentDataWidget 中会携带某类渲染对象感兴趣的数据,一般用于特定的组件。比如 Positioned 组件用于 Stack 之中,Flexible 用于 Flex 之中等。
  • 它本身不打扰布局、不渲染画面,但一旦数据变了,它能精确告诉哪些组件需要更新。

这就像一条无形的数据高速公路,把状态、样式、主题等信息安全、高效地送达目的地。ProxyWidget 专注于功能注入和状态传递,不关心 UI 外观,只专注于控制行为和结构。


4. 存在感不高的 PreferredSizeWidget

相比于前面耳熟能详的组件,PreferredSizeWidget 似乎没什么人在意过。当有些组件想要对自身尺寸 "有个交代" 时,比如 AppBarTabBar 等 ,需要让父组件 Scaffold 知道它多高,以便正确布局整个页面。这就是 PreferredSizeWidget 的使用场景

PreferredSizeWidget 是一个抽象接口,它的职责是:

提供一个首选尺寸(preferredSize),供父组件在布局时参考。

注意,这只是一个建议尺寸,父组件可以采纳,也可以无视。它不会直接影响自身大小,而是为布局"提供建议"。这种"报备尺寸"的机制,在 Flutter 中使用场景虽然不多,但非常关键 ------ 它是组合型组件与布局系统之间的一种契约协商方式。


5. 一切的起点 RootWidget

如果打开渲染组件树,可以看到最顶层的 root 对应的组件是 RootWidget ,它是由 Flutter 框架内部创建的树的起点。知不知道它,对应用层开发没什么影响。但理解 RootWidget 的诞生,对了解 Flutter 框架的原理非常重要,以后会作为单独的一题讨论。


6.本题小结

Flutter 的 Widget 派生体系,看似杂乱无章,但站在更高的角度审视时,可以看到精巧设计下的责任划分。不同类型的 Widget 各司其职,构建起高效、声明式的 UI 架构:

  • StatelessWidget 与 StatefulWidget 是我们日常构建 UI 的主力军,它们提供了组合式开发的入口,关注"描述"与"响应";
  • RenderObjectWidget 负责创建和维护真实的渲染对象,提供功能组件的天命打工人;
  • ProxyWidget 则打通了数据与行为的传输通道,确保信息在 Widget 树中流动顺畅,相当于数据管道
  • PreferredSizeWidget 则为那些需要尺寸沟通的组件建立了契约机制,让 UI 能更智能地协同布局;
  • 最终,一切都始于 RootWidget ------ Flutter 应用启动的锚点,它是 Widget 树与底层引擎的结合点。

这就构成了 Widget 的派生体系 :将 UI 拆解为职责清晰、协作高效的部件,既提升了开发者的自由度,也为高性能的跨平台渲染奠定了基础。理解这些派生体系,不只是为了写出更多的 Widget,更是为了看清 Flutter 背后的设计思路 ------ 每一层都是为了让 UI 更轻、更快、更可维护。


如果你有其他的看法,或者有什么想要的题目、或者想提供题目和答案,都欢迎在评论区留言。更多文章和视频知识资讯,大家可以关注我的公众号、掘金和 B 站 。

相关推荐
louisgeek6 分钟前
Git 使用 SSH 连接
android
二流小码农13 分钟前
鸿蒙开发:实现一个标题栏吸顶
android·ios·harmonyos
京东零售技术26 分钟前
在京东 探索技术的无限可能
面试
阅文作家助手开发团队_山神1 小时前
第三章: Flutter-quill 数据格式Delta
flutter
八月林城1 小时前
echarts在uniapp中使用安卓真机运行时无法显示的问题
android·uni-app·echarts
阅文作家助手开发团队_山神1 小时前
第二章:Document 模块与 DOM 树详解
flutter
雨白1 小时前
搞懂 Fragment 的生命周期
android
寒山李白1 小时前
Java 依赖注入、控制反转与面向切面:面试深度解析
java·开发语言·面试·依赖注入·控制反转·面向切面
casual_clover2 小时前
Android 之 kotlin语言学习笔记三(Kotlin-Java 互操作)
android·java·kotlin
程序员老刘2 小时前
20%的选择决定80%的成败
flutter·架构·客户端