在 Flutter 的世界里,记住一句箴言:"万物皆 Widget"。您在屏幕上看到的一切------按钮、文本、图片、甚至整个页面布局------都是由一个个 Widget 嵌套组合而成的。
然而,这些 Widget 并非生而平等。它们主要分为两大阵营:StatelessWidget (无状态组件) 和 StatefulWidget (有状态组件)。理解它们的区别,是构建任何复杂 Flutter 应用的基石。
学习目标
- 理解什么是 "状态" (State)。
- 掌握
StatelessWidget的特点和使用场景。 - 掌握
StatefulWidget的结构、生命周期和setState的核心作用。 - 学会根据需求选择合适的 Widget 类型。
1. 什么是 "状态" (State)?
在 Flutter 中,"状态" 指的是可以在 Widget 生命周期内发生改变的数据。简单来说,任何可能变化并影响 UI 展现的信息,都是状态。
- 例如:复选框是否被选中、计数器当前的数字、滑动条的位置、用户在输入框中输入的文字等。
根据一个 Widget 能否在其内部管理这些可变的状态,我们将其划分为 StatelessWidget 和 StatefulWidget。
2. StatelessWidget (无状态组件)
顾名思义,StatelessWidget 自身是没有 "状态" 的。
- 核心特点 :一旦被创建,其内部的数据和外观就不可改变 (immutable)。它只负责根据创建时传入的配置信息进行 "静态" 渲染。
- 生命周期 :非常简单,只有构造函数和
build方法。它被渲染后,就不会再主动重绘自己。 - 工作方式:像一个"画匠",你告诉它要画什么(通过构造函数传递参数),它就一次性画好,然后就不再管了。如果想让它画出不同的内容,唯一的办法就是销毁它,然后用新的参数创建一个新的实例。
何时使用 StatelessWidget?
当你构建的 UI 部分不依赖于任何内部可变的数据,只依赖于外部传入的配置时,就应该使用 StatelessWidget。
- 常见场景 :
Icon、Text、RaisedButton(按钮本身)、静态的图片、应用的 Logo、一个设计好的卡片模板等。
代码示例:创建一个静态的用户信息卡片
让我们创建一个简单的 UserInfoCard,它接收一个名字和等级,然后显示出来。这个卡片一旦显示,内容就是固定的。
less
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('StatelessWidget Demo')),
body: const Center(
// 使用我们自定义的 StatelessWidget
child: UserInfoCard(
name: 'Flutter Coder',
level: 'Expert',
avatarColor: Colors.blue,
),
),
),
);
}
}
// 这是我们的自定义 StatelessWidget
class UserInfoCard extends StatelessWidget {
// 属性一旦设置,就不能再改变 (final)
final String name;
final String level;
final Color avatarColor;
// 构造函数,接收外部传入的数据
const UserInfoCard({
super.key,
required this.name,
required this.level,
this.avatarColor = Colors.grey,
});
@override
Widget build(BuildContext context) {
// build 方法描述了 UI 的样子
// 它只依赖于构造函数传入的 final 变量
return Container(
padding: const EdgeInsets.all(16.0),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(10.0),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.3),
spreadRadius: 2,
blurRadius: 5,
),
],
),
child: Row(
mainAxisSize: MainAxisSize.min, // 让 Row 的宽度自适应内容
children: [
CircleAvatar(
backgroundColor: avatarColor,
radius: 30,
child: const Icon(Icons.person, color: Colors.white, size: 30),
),
const SizedBox(width: 16), // 用于添加间距
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
name,
style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
),
Text(
level,
style: const TextStyle(fontSize: 16, color: Colors.grey),
),
],
),
],
),
);
}
}
在这个例子中,UserInfoCard 的 name 和 level 是通过构造函数传入的,并且被 final 关键字修饰,意味着它们在 Widget 内部是不可变的。build 方法只负责把这些数据显示出来。
3. StatefulWidget (有状态组件)
与 StatelessWidget 相对,StatefulWidget 自身可以持有和管理可变的状态 (State)。
- 核心特点 :当其内部状态改变时,它可以主动触发自身重绘 (rebuild),从而更新 UI 显示。
- 结构 :
StatefulWidget比较特殊,它由两个类 组成:- 一个继承自
StatefulWidget的类:这个类本身是不可变的,只负责存储一些最终配置 (类似StatelessWidget)。它最重要的方法是createState()。 - 一个继承自
State的类:这个类才是核心,它负责持有可变的状态数据 ,并且包含了build方法来构建 UI。这个State对象可以在多次build之间保持存活。
- 一个继承自
为什么需要两个类? 这是 Flutter 的一种性能优化设计。当 Flutter 重建 UI 时,
StatefulWidget的实例可能会被销毁和重建,但它的State对象会被保留下来,这样就可以在多次重建之间保持状态数据不丢失。
State 对象的生命周期和核心方法
initState():State对象被创建后调用的第一个方法,且只调用一次。通常在这里执行一些初始化操作,如初始化变量、订阅事件等。build(): 在initState()之后立即调用,并且每当 UI 需要更新时都会被调用。setState(VoidCallback fn): 最核心的方法! 当你想改变状态并让 UI 更新时,你必须 在setState的回调函数中修改你的状态变量。调用setState()会通知 Flutter:"嘿,我这儿有数据变了,请重新调用我的build方法来更新屏幕!"dispose(): 当 Widget 被从 Widget 树中永久移除时调用。通常在这里执行清理工作,如取消定时器、移除监听器等,以防内存泄漏。
代码示例:一个经典的计数器
这个例子完美地展示了 StatefulWidget 如何管理自己的状态。
scala
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('StatefulWidget Demo')),
body: const Center(
// 使用我们自定义的 StatefulWidget
child: Counter(),
),
),
);
}
}
// 1. 继承自 StatefulWidget 的类 (不可变的部分)
class Counter extends StatefulWidget {
const Counter({super.key});
// 核心方法:创建与 Widget 关联的 State 对象
@override
State<Counter> createState() => _CounterState();
}
// 2. 继承自 State 的类 (持有可变状态并构建 UI 的部分)
// 类名前的下划线 `_` 表示这是私有的,只能在当前文件中访问
class _CounterState extends State<Counter> {
// 这就是我们的 "状态":一个可以改变的计数器变量
int _counter = 0;
// 一个改变状态的方法
void _incrementCounter() {
// 必须在 setState 中修改状态,这样 Flutter 才知道需要更新 UI
setState(() {
// 在这个回调函数里,我们改变 _counter 的值
_counter++;
});
}
// State 对象的 build 方法,用于构建 UI
@override
Widget build(BuildContext context) {
// 每当 setState 被调用,这个 build 方法就会被重新执行
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'You have pushed the button this many times:',
style: TextStyle(fontSize: 18),
),
Text(
'$_counter', // UI 直接显示状态变量 _counter 的值
style: const TextStyle(fontSize: 40, fontWeight: FontWeight.bold),
),
const SizedBox(height: 20),
ElevatedButton(
onPressed: _incrementCounter, // 按下按钮时,调用改变状态的方法
child: const Icon(Icons.add),
),
],
);
}
}
在这个例子中,每次点击按钮,_incrementCounter 方法被调用。方法内部的 setState() 告诉 Flutter 框架 _counter 变量已经改变了。于是,Flutter 会重新调用 _CounterState 的 build 方法,新的 _counter 值被渲染到屏幕上,你就看到了数字的增加。
4. 如何选择:Stateless 还是 Stateful?
问自己一个简单的问题:"这个 Widget 在被渲染出来后,它的显示内容是否需要根据用户交互或内部数据变化而改变?"
- 如果答案是"否" ,它只是一个静态的展示,那就用
StatelessWidget。 - 如果答案是"是" ,比如它包含一个需要用户输入的表单,或者一个可以点击切换状态的按钮,那就用
StatefulWidget。
经验法则 :优先使用 StatelessWidget。只有当你确定需要管理内部状态时,才将其重构为 StatefulWidget。这会让你的应用结构更清晰,性能也更好。
总结
| 特性 | StatelessWidget | StatefulWidget |
|---|---|---|
| 状态 | 不可变 (Immutable) | 可变 (Mutable) |
| 核心 | 只是一个 build 方法 |
State 对象 + setState() |
| 结构 | 单一类 | 两个类 (Widget + State) |
| 生命周期 | 简单(构造函数 + build) |
复杂 (initState, build, dispose等) |
| 用途 | 静态展示UI,如 Icon, Text |
动态UI,响应交互和数据变化 |
至此,您已经掌握了 Flutter 中最核心的 Widget 概念。理解了 StatelessWidget 和 StatefulWidget 的区别,就等于拿到了开启 Flutter 大门的钥匙。
接下来,我们将学习如何使用 Flutter 提供的各种布局 Widget (如 Container, Row, Column 等) 来像搭积木一样组合出漂亮的界面。我们下一篇教程见!