Skip to content

Widget 基础

从「Widget 是什么」到「每行代码为什么这样写」,一篇搞定。看完后你能手写 StatelessWidget 和 StatefulWidget,不再依赖快捷生成。

一、什么是 Widget

在 Flutter 中,一切皆 Widget。Widget 是描述 UI 的不可变对象:

  • 组件类 —— 可见的 UI 元素:TextImageIconButton
  • 布局类 —— 控制排列方式:ColumnRowStackListView
  • 装饰类 —— 添加视觉效果:PaddingOpacityClipRRect

核心理念

Flutter 是声明式的——你描述「UI 应该长什么样」,数据变化时框架自动重建受影响的 Widget 子树。

二、从 runApp 开始

Widget 树

Flutter 应用由一棵嵌套的 Widget 树构成:

MaterialApp
└── Scaffold
    ├── AppBar
    │   └── Text("标题")
    ├── Center
    │   └── Column
    │       ├── Image
    │       └── Text("内容")
    └── FloatingActionButton
        └── Icon(Icons.add)

runApp 解码

runApp() 把一个 Widget 挂载为这棵树的根:

dart
void main() {
  runApp(const CounterApp());
  //   ↑1     ↑2   ↑3          ↑4
}
部分含义
runApp()Flutter 启动函数,把 Widget 挂载到屏幕作为整棵树的根
const编译期常量,框架可复用同一对象
CounterApp你自定义的 Widget 类名
()调用构造函数,创建实例

一句话:启动 Flutter 应用,根组件是一个常量的 CounterApp 实例。

三、读懂 Widget 代码 — 通用语法解码

StatelessWidget 和 StatefulWidget 共享一套语法模式。理解这些,两种 Widget 你都能手写。

3.1 构造函数逐字解码

以一个典型的构造函数为例:

dart
const MyWidget({
  super.key,
  required this.title,
  this.subtitle,
});

const — 常量构造函数

dart
const MyWidget(...)   // ← 这个 const 写在构造函数定义上

含义:这个构造函数创建的对象可以是编译期常量

好处:当多处使用 const MyWidget(title: 'hi') 时,Flutter 只在内存中创建一个对象,所有地方共享,节省内存。

规则:想让构造函数加 const,类中所有属性必须是 final

{ } — 命名参数

dart
MyWidget({ this.title, this.subtitle })
//       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 花括号 = 命名参数

花括号表示调用时必须写参数名

dart
MyWidget(title: '你好')               // ✅ 命名参数,写参数名
MyWidget('你好')                       // ❌ 位置参数,不允许
MyWidget(title: '你好', subtitle: '副标题') // ✅ 多个参数

super.key — 把 key 传给父类

dart
const MyWidget({ super.key });

这是 Dart 的简写语法,完整写法是:

dart
const MyWidget({ Key? key }) : super(key: key);

作用:把 key 参数传给父类 Widgetkey 是 Flutter 用于识别 Widget 的标识,大多数时候你不需要管它,写上 super.key 就行。

required — 必传参数

dart
required this.title,   // 调用时必须传
this.subtitle,         // 调用时可以不传(可选)
修饰含义调用示例
required必传,不传会编译报错MyWidget(title: '你好') ✅ / MyWidget()
required可选,不传时为 nullMyWidget(title: '你好')

this.title — 简写赋值

dart
this.title   // 等价于:把传入的 title 参数值赋给 this.title 属性

完整写法对比:

dart
// 简写(推荐)
const MyWidget({ required this.title });

// 完整写法(等价)
const MyWidget({ required String title }) : title = title;

可选参数与可空类型

可选参数不传时为 null,所以类型必须加 ? 表示可空:

dart
final String title;     // 必传,非空
final String? subtitle; // 可选,可空(注意问号)

3.2 @override — 重写标记

dart
@override
Widget build(BuildContext context) { ... }

含义:我正在重写父类的方法。

  • 父类定义了 build() 方法(但没有实现)
  • 子类重新实现了它
  • @override,编译器帮你检查:父类是否真有这个方法?没有就报错

不写会怎样? 功能上不影响运行,但如果你拼错了方法名(如 biuld),没有 @override 编译器不会提醒,方法永远不被调用,UI 不显示,还很难排查 bug。

一句话:@override 是防拼写错误的保险。

3.3 build 方法 — UI 描述

dart
@override
Widget build(BuildContext context) {
//    ↑返回值   ↑方法名   ↑参数
  return Text('hello');
}
部分含义
Widget 返回值告诉 Flutter 渲染什么
build 方法名Flutter 框架约定的名字,框架会自动调用
BuildContext contextWidget 在树中的"身份证",用来获取上下文信息

3.4 BuildContext — 上下文信息

context 就是「我在 Widget 树中的位置」,有了它才能找到主题、屏幕信息、导航等上下文:

dart
Widget build(BuildContext context) {
  final theme = Theme.of(context);          // 获取主题
  final size = MediaQuery.of(context).size; // 获取屏幕尺寸
  Navigator.of(context).push(...);          // 页面跳转
  ScaffoldMessenger.of(context).showSnackBar(...); // 显示提示
  return Text('Hello', style: TextStyle(color: theme.colorScheme.primary));
}

3.5 const 优化 — 何时能加 const

构造函数定义上的 const 和实例化时的 const 是配套的——只有当所有参数都是编译时常量时,实例化才能加 const

dart
// ✅ 可以加 const(参数全是字面量/编译时常量)
const Text('hello')
const SizedBox(height: 10)
const EdgeInsets.all(8)
const Icon(Icons.home)

// ❌ 不能加 const(含运行时变量)
Text(name)              // name 是变量
SizedBox(height: x)     // x 是变量
Text('计数: $_counter')  // 字符串插值含变量

不确定?不加也能跑,只是少了点优化。Dart 的 lint 规则会自动提示你该加的地方。

四、StatelessWidget — 不可变组件

创建后不可变,一旦构建就不会再改变。适合纯展示型组件。

4.1 基本结构

dart
// ─── ★ StatelessWidget 三段式 ──────────────
class MyWidget extends StatelessWidget {
  // 1️⃣ 构造函数(通常加 const)
  const MyWidget({
    super.key,
    required this.title,
    this.subtitle,
  });

  // 2️⃣ 属性(final,不可变)
  final String title;
  final String? subtitle;

  // 3️⃣ build 方法 —— 描述 UI
  @override
  Widget build(BuildContext context) {
    return Text(title);
  }
}
// ─── ☆ StatelessWidget 三段式 ──────────────

三段式口诀:构造函数 → final 属性 → build 方法

4.2 关键规则

规则说明
构造函数用 const允许框架复用实例,提升性能
属性用 final属性在构建后不可变
build() 是纯函数相同输入 → 相同输出,不应有副作用
不依赖 setState()无状态,不会主动更新
可以接收回调父组件通过回调来通知事件

4.3 通过回调和父组件交互

StatelessWidget 自身没有状态,但可以通过回调将事件传递给父组件:

dart
class CounterButton extends StatelessWidget {
  const CounterButton({
    super.key,
    required this.onPressed,
    this.label = '+1',
  });

  // ─── ★ 回调函数参数 ──────────────
  final VoidCallback onPressed;
  // ─── ☆ 回调函数参数 ──────────────
  final String label;

  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      onPressed: onPressed,
      child: Text(label),
    );
  }
}

4.4 完整示例

dart
import 'package:flutter/material.dart';

class UserCard extends StatelessWidget {
  const UserCard({
    super.key,
    required this.name,
    required this.avatar,
    this.bio,
  });

  final String name;
  final String avatar;
  final String? bio;

  @override
  Widget build(BuildContext context) {
    return Card(
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Row(
          children: [
            CircleAvatar(
              backgroundImage: NetworkImage(avatar),
              radius: 30,
            ),
            const SizedBox(width: 16),
            Expanded(
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Text(
                    name,
                    style: Theme.of(context).textTheme.titleMedium,
                  ),
                  if (bio != null)
                    Text(
                      bio!,
                      style: Theme.of(context).textTheme.bodySmall,
                    ),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }
}

// 使用
UserCard(
  name: '张三',
  avatar: 'https://example.com/avatar.jpg',
  bio: 'Flutter 开发者',
)

五、StatefulWidget — 可变组件

通过内部的 State 对象管理可变数据,适合需要交互或响应变化的组件。

5.1 基本结构

StatefulWidget 由两个类组成:

dart
// ── Widget 类:声明接口与不可变配置 ──
// ─── ★ StatefulWidget Widget 类 ──────────────
class MyWidget extends StatefulWidget {
  const MyWidget({super.key, required this.title});

  final String title;

  @override
  State<MyWidget> createState() => _MyWidgetState();
}
// ─── ☆ StatefulWidget Widget 类 ──────────────

// ── State 类:管理可变状态与 UI 构建 ──
// ─── ★ State 类管理可变状态 ──────────────
class _MyWidgetState extends State<MyWidget> {
  // ─── ★ 可变状态 ──────────────
  int _count = 0;
  // ─── ☆ 可变状态 ──────────────

  void _increment() {
    // ─── ★ setState 触发重建 ──────────────
    setState(() => _count++);
    // ─── ☆ setState 触发重建 ──────────────
  }

  @override
  Widget build(BuildContext context) {
    return Text('${widget.title}: $_count');
  }
}
// ─── ☆ State 类管理可变状态 ──────────────

5.2 为什么是两个类

这是初学者最困惑的问题。核心原因:不可变与可变必须分离

┌──────────────────────────┐     ┌──────────────────────────┐
│  MyWidget (Widget 类)     │     │  _MyWidgetState (State 类)│
│                          │     │                          │
│  ✅ final 属性(不可变)    │     │  ✅ 可变状态              │
│  ✅ 构造函数               │     │  ✅ build() 方法          │
│  ✅ createState()         │     │  ✅ setState() 触发重建    │
│                          │     │                          │
│  🔄 每次重建会创建新实例    │     │  🔄 框架会复用同一个实例    │
└──────────────────────────┘     └──────────────────────────┘
        创建时传入                       持久保存在内存中
角色比喻职责
Widget 类蓝图 / 配置单描述"长什么样",不可变的快照
State 类工人 / 实例根据蓝图干活,持有可变数据,负责渲染

setState() 被调用时:

  • Widget 会被重新创建(新的配置单)
  • State 不会销毁(同一个工人拿到新蓝图继续干活)

这就是为什么 State 中的 _count 不会丢失——State 是持久的

5.3 几个细节问题

_MyWidgetState 前面为什么有下划线

Dart 中下划线 _ 开头表示私有,外部文件无法访问。State 是内部实现细节,不需要暴露给外部。

State<MyWidget> 中的泛型

dart
class _MyWidgetState extends State<MyWidget> {
//                                       ^^^^^^^^^ 绑定到 MyWidget

泛型让 State 知道自己属于哪个 Widget,这样你才能用 widget.title 访问 Widget 的属性。

widget. 是什么

在 State 类中,widget 是内置属性,指向绑定的 Widget 实例

dart
class _MyWidgetState extends State<MyWidget> {
  @override
  Widget build(BuildContext context) {
    return Text(widget.title);  // 访问 MyWidget 的 title 属性
  }
}

5.4 setState() — 触发 UI 更新

dart
void _increment() {
  setState(() {
    _count++;               // 修改状态变量
  });
  // setState 结束后,框架自动调用 build() 重建 UI
}

注意

setState() 内部只应修改状态变量,不要执行耗时操作(网络请求、IO 等)。

六、VSCode 快捷生成

手写 Widget 代码是基本功,但日常开发中用快捷方式能大幅提速。

快捷输入生成内容适用场景
stl + TabStatelessWidget 完整模板不可变组件
stf + TabStatefulWidget 完整模板(含 State 类)可变组件

stl 生成结果:

dart
class  extends StatelessWidget {
  const ({super.key});

  @override
  Widget build(BuildContext context) {
    return const Placeholder();
  }
}

stf 生成结果:

dart
class  extends StatefulWidget {
  const ({super.key});

  @override
  State<> createState() => _State();
}

class _State extends State<> {
  @override
  Widget build(BuildContext context) {
    return const Placeholder();
  }
}

光标会自动定位到类名处,填入类名即可。

建议

先学手写、理解每行含义,再使用快捷方式提速。这样出问题时你能快速定位,而不是对着模板发呆。

七、StatelessWidget vs StatefulWidget — 如何选择

场景推荐方案
纯展示,不需要变化StatelessWidget
需要内部状态(如计数器、开关)StatefulWidget
状态由父组件管理,子组件只负责展示StatelessWidget + 回调
状态需要在多个组件间共享状态管理方案(Provider 等)

经验法则

  1. 默认使用 StatelessWidget —— 简单、性能好
  2. 只有组件确实需要管理自身可变状态时,才用 StatefulWidget
  3. 如果状态可以提升到父组件,优先用 StatelessWidget + 回调
  4. 多组件共享状态时,考虑状态管理方案

八、速查表

概念一句话解释
extends StatelessWidget继承无状态组件,数据不变,一个类搞定
extends StatefulWidget继承有状态组件,需要两个类配合
const 构造函数常量构造函数,相同参数只创建一个对象,提升性能
{ } 命名参数调用时必须写参数名,更清晰
required强制必传,不传编译报错
this.xxx简写赋值,把参数值赋给同名属性
super.key简写,把 key 传给父类 Widget
@override标记重写父类方法,防拼写错误
Widget build(BuildContext context)框架自动调用的方法,返回 UI
BuildContextWidget 在树中的位置信息,用来获取上下文
State<MyWidget>State 绑定到哪个 Widget,才能用 widget. 访问属性
widget.xxx在 State 中访问对应 Widget 的属性
setState()通知框架状态变了,重新调用 build()
_MyWidgetState下划线开头 = 私有类,外部文件不可访问
runApp()Flutter 启动函数,把 Widget 挂载到屏幕
stlVSCode 快捷键,生成 StatelessWidget 模板
stfVSCode 快捷键,生成 StatefulWidget 模板

下一步

基于 Flutter 官方文档整理