Widget 基础
从「Widget 是什么」到「每行代码为什么这样写」,一篇搞定。看完后你能手写 StatelessWidget 和 StatefulWidget,不再依赖快捷生成。
一、什么是 Widget
在 Flutter 中,一切皆 Widget。Widget 是描述 UI 的不可变对象:
- 组件类 —— 可见的 UI 元素:
Text、Image、Icon、Button - 布局类 —— 控制排列方式:
Column、Row、Stack、ListView - 装饰类 —— 添加视觉效果:
Padding、Opacity、ClipRRect
核心理念
Flutter 是声明式的——你描述「UI 应该长什么样」,数据变化时框架自动重建受影响的 Widget 子树。
二、从 runApp 开始
Widget 树
Flutter 应用由一棵嵌套的 Widget 树构成:
MaterialApp
└── Scaffold
├── AppBar
│ └── Text("标题")
├── Center
│ └── Column
│ ├── Image
│ └── Text("内容")
└── FloatingActionButton
└── Icon(Icons.add)runApp 解码
runApp() 把一个 Widget 挂载为这棵树的根:
void main() {
runApp(const CounterApp());
// ↑1 ↑2 ↑3 ↑4
}| 部分 | 含义 |
|---|---|
① runApp() | Flutter 启动函数,把 Widget 挂载到屏幕作为整棵树的根 |
② const | 编译期常量,框架可复用同一对象 |
③ CounterApp | 你自定义的 Widget 类名 |
④ () | 调用构造函数,创建实例 |
一句话:启动 Flutter 应用,根组件是一个常量的 CounterApp 实例。
三、读懂 Widget 代码 — 通用语法解码
StatelessWidget 和 StatefulWidget 共享一套语法模式。理解这些,两种 Widget 你都能手写。
3.1 构造函数逐字解码
以一个典型的构造函数为例:
const MyWidget({
super.key,
required this.title,
this.subtitle,
});const — 常量构造函数
const MyWidget(...) // ← 这个 const 写在构造函数定义上含义:这个构造函数创建的对象可以是编译期常量。
好处:当多处使用 const MyWidget(title: 'hi') 时,Flutter 只在内存中创建一个对象,所有地方共享,节省内存。
规则:想让构造函数加
const,类中所有属性必须是final。
{ } — 命名参数
MyWidget({ this.title, this.subtitle })
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 花括号 = 命名参数花括号表示调用时必须写参数名:
MyWidget(title: '你好') // ✅ 命名参数,写参数名
MyWidget('你好') // ❌ 位置参数,不允许
MyWidget(title: '你好', subtitle: '副标题') // ✅ 多个参数super.key — 把 key 传给父类
const MyWidget({ super.key });这是 Dart 的简写语法,完整写法是:
const MyWidget({ Key? key }) : super(key: key);作用:把 key 参数传给父类 Widget。key 是 Flutter 用于识别 Widget 的标识,大多数时候你不需要管它,写上 super.key 就行。
required — 必传参数
required this.title, // 调用时必须传
this.subtitle, // 调用时可以不传(可选)| 修饰 | 含义 | 调用示例 |
|---|---|---|
required | 必传,不传会编译报错 | MyWidget(title: '你好') ✅ / MyWidget() ❌ |
无 required | 可选,不传时为 null | MyWidget(title: '你好') ✅ |
this.title — 简写赋值
this.title // 等价于:把传入的 title 参数值赋给 this.title 属性完整写法对比:
// 简写(推荐)
const MyWidget({ required this.title });
// 完整写法(等价)
const MyWidget({ required String title }) : title = title;可选参数与可空类型
可选参数不传时为 null,所以类型必须加 ? 表示可空:
final String title; // 必传,非空
final String? subtitle; // 可选,可空(注意问号)3.2 @override — 重写标记
@override
Widget build(BuildContext context) { ... }含义:我正在重写父类的方法。
- 父类定义了
build()方法(但没有实现) - 子类重新实现了它
- 加
@override,编译器帮你检查:父类是否真有这个方法?没有就报错
不写会怎样? 功能上不影响运行,但如果你拼错了方法名(如 biuld),没有 @override 编译器不会提醒,方法永远不被调用,UI 不显示,还很难排查 bug。
一句话:@override 是防拼写错误的保险。
3.3 build 方法 — UI 描述
@override
Widget build(BuildContext context) {
// ↑返回值 ↑方法名 ↑参数
return Text('hello');
}| 部分 | 含义 |
|---|---|
Widget 返回值 | 告诉 Flutter 渲染什么 |
build 方法名 | Flutter 框架约定的名字,框架会自动调用它 |
BuildContext context | Widget 在树中的"身份证",用来获取上下文信息 |
3.4 BuildContext — 上下文信息
context 就是「我在 Widget 树中的位置」,有了它才能找到主题、屏幕信息、导航等上下文:
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:
// ✅ 可以加 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 基本结构
// ─── ★ 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 自身没有状态,但可以通过回调将事件传递给父组件:
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 完整示例
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 由两个类组成:
// ── 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> 中的泛型
class _MyWidgetState extends State<MyWidget> {
// ^^^^^^^^^ 绑定到 MyWidget泛型让 State 知道自己属于哪个 Widget,这样你才能用 widget.title 访问 Widget 的属性。
widget. 是什么
在 State 类中,widget 是内置属性,指向绑定的 Widget 实例:
class _MyWidgetState extends State<MyWidget> {
@override
Widget build(BuildContext context) {
return Text(widget.title); // 访问 MyWidget 的 title 属性
}
}5.4 setState() — 触发 UI 更新
void _increment() {
setState(() {
_count++; // 修改状态变量
});
// setState 结束后,框架自动调用 build() 重建 UI
}注意
setState() 内部只应修改状态变量,不要执行耗时操作(网络请求、IO 等)。
六、VSCode 快捷生成
手写 Widget 代码是基本功,但日常开发中用快捷方式能大幅提速。
| 快捷输入 | 生成内容 | 适用场景 |
|---|---|---|
stl + Tab | StatelessWidget 完整模板 | 不可变组件 |
stf + Tab | StatefulWidget 完整模板(含 State 类) | 可变组件 |
stl 生成结果:
class extends StatelessWidget {
const ({super.key});
@override
Widget build(BuildContext context) {
return const Placeholder();
}
}stf 生成结果:
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 等) |
经验法则
- 默认使用 StatelessWidget —— 简单、性能好
- 只有组件确实需要管理自身可变状态时,才用 StatefulWidget
- 如果状态可以提升到父组件,优先用 StatelessWidget + 回调
- 多组件共享状态时,考虑状态管理方案
八、速查表
| 概念 | 一句话解释 |
|---|---|
extends StatelessWidget | 继承无状态组件,数据不变,一个类搞定 |
extends StatefulWidget | 继承有状态组件,需要两个类配合 |
const 构造函数 | 常量构造函数,相同参数只创建一个对象,提升性能 |
{ } 命名参数 | 调用时必须写参数名,更清晰 |
required | 强制必传,不传编译报错 |
this.xxx | 简写赋值,把参数值赋给同名属性 |
super.key | 简写,把 key 传给父类 Widget |
@override | 标记重写父类方法,防拼写错误 |
Widget build(BuildContext context) | 框架自动调用的方法,返回 UI |
BuildContext | Widget 在树中的位置信息,用来获取上下文 |
State<MyWidget> | State 绑定到哪个 Widget,才能用 widget. 访问属性 |
widget.xxx | 在 State 中访问对应 Widget 的属性 |
setState() | 通知框架状态变了,重新调用 build() |
_MyWidgetState | 下划线开头 = 私有类,外部文件不可访问 |
runApp() | Flutter 启动函数,把 Widget 挂载到屏幕 |
stl | VSCode 快捷键,生成 StatelessWidget 模板 |
stf | VSCode 快捷键,生成 StatefulWidget 模板 |
