在 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
等) 来像搭积木一样组合出漂亮的界面。我们下一篇教程见!