1. 前言
在上一篇博客中,我们成功搭建了 Flutter 环境,并运行了一个简单的“Hello World”。这一篇将正式进入 Flutter 的核心世界 —— Widget(组件) 。
在 Flutter 中,一切皆 Widget。无论是屏幕上的一个按钮、一段文字、一张图片,还是控制布局的行列、内边距,甚至整个应用本身,都是 Widget。理解 Widget 的工作机制,是写出 Flutter 应用的基础。
Widget 分为两大类:无状态 Widget(StatelessWidget) 和 有状态 Widget(StatefulWidget) 。这也是刚接触 Flutter 时最容易感到困惑的地方。下面我会用通俗易懂的方式解释,并给出一个可运行的例子。
2. 无状态 Widget(StatelessWidget)
含义:一旦创建,就不会再改变。它的外观和内容是固定的,不需要响应用户交互或内部数据变化。
典型场景:静态文本、图标、头像、展示性的卡片、布局结构等。
例子:一个显示“欢迎学习 Flutter”文字的组件。
import 'package:flutter/material.dart';
class GreetingWidget extends StatelessWidget {
final String name;
// 构造函数,接收外部传入的参数
const GreetingWidget({super.key, required this.name});
@override
Widget build(BuildContext context) {
return Text('你好,$name!欢迎学习 Flutter。');
}
}
这个组件一旦用 GreetingWidget(name: '小明') 创建,显示的文字就固定为“你好,小明……”,除非父组件用新的参数重新创建它,否则它自己无法主动改变。
3. 有状态 Widget(StatefulWidget)
含义:组件内部拥有可以变化的状态(State)。当状态改变时,组件会自动重新构建 UI,体现出新的外观。
典型场景:计数器、表单输入框、复选框、动画、倒计时等需要动态变化的界面。
特点:编写一个 StatefulWidget 实际上需要 两个类:
- StatefulWidget 类本身(不可变,负责创建 State)
- State 类(可变,存放状态并编写
build方法)
例子:一个点击按钮后文字会改变的组件。
class ChangeableTextWidget extends StatefulWidget {
const ChangeableTextWidget({super.key});
@override
State<ChangeableTextWidget> createState() => _ChangeableTextWidgetState();
}
class _ChangeableTextWidgetState extends State<ChangeableTextWidget> {
String displayText = "还没有点击按钮";
void _changeText() {
setState(() {
displayText = "按钮被点击了!文字已更新。";
});
}
@override
Widget build(BuildContext context) {
return Column(
children: [
Text(displayText, style: const TextStyle(fontSize: 20)),
ElevatedButton(
onPressed: _changeText,
child: const Text("点我改变文字"),
),
],
);
}
}
关键点在于 setState 方法。它告诉 Flutter:“我内部的状态(这里是 displayText)发生了变化,请重新执行 build 方法刷新界面。”
4. 两者对比总结
| StatelessWidget | StatefulWidget | |
|---|---|---|
| 状态 | 无内部可变状态 | 拥有内部可变状态(State) |
| UI 能否自改变 | 不能 | 能(通过 setState) |
| 类数量 | 1 个 | 2 个(Widget + State) |
| 生命周期 | 只有 build | 有 initState、didUpdateWidget、dispose 等 |
| 使用场景 | 静态内容、展示型 UI | 交互、动态数据、动画 |
简单记忆: “不变用 Stateless,要变用 Stateful” 。当一个组件是否需要根据用户操作、网络请求、定时器等改变外观时,就要用 StatefulWidget。
5. 动手实验:改造上一篇的“Hello World”
上一篇的“Hello World”是一个无状态组件(直接在 MyApp 里返回了 Scaffold)。我们可以把它改成一个有状态的例子:点击屏幕中央的文字,文字内容在“Hello, Flutter!”和“你点了我一下 😄”之间切换。
将 lib/main.dart 中的 MyHomePage 替换成以下代码:
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: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
String message = "Hello, Flutter!";
bool isToggled = false;
void _toggleMessage() {
setState(() {
isToggled = !isToggled;
message = isToggled ? "你点了我一下 😄" : "Hello, Flutter!";
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('第二篇博客:状态管理初探'),
),
body: Center(
child: GestureDetector(
onTap: _toggleMessage,
child: Container(
padding: const EdgeInsets.all(20),
color: Colors.blue[100],
child: Text(
message,
style: const TextStyle(fontSize: 28, fontWeight: FontWeight.bold),
),
),
),
),
);
}
}
运行效果:初始显示蓝色的背景块和“Hello, Flutter!”文字。点击文字区域,文字会切换成“你点了我一下 😄”,再次点击变回原样。
这里用到了 GestureDetector 来检测点击,你也可以改用 ElevatedButton。核心是 setState 触发了界面的重新构建。
6. 一个常见误区
误区:我直接在 State 里修改变量,不调用
setState,界面为什么不刷新?
原因:Flutter 并不知道变量的值变了。setState 内部会标记当前组件为“脏”,在下一次帧刷新时重新调用 build。直接修改而不调用 setState,界面不会发生任何变化。
正确做法:所有影响 UI 的状态变化,都必须放在 setState 中执行。
7. 下一步计划
- 学习常用的布局组件:
Row、Column、Stack、Container - 了解状态管理的演变(
setState→Provider→Riverpod) - 尝试写一个简单的待办事项列表(加深对 StatefulWidget 的理解)
8. 本篇小结
本篇博客我们学习了:
- 什么是 Widget,以及 Flutter 中“一切皆 Widget”的思想
- 无状态组件(StatelessWidget)和有状态组件(StatefulWidget)的区别
- 如何用
setState让界面动起来 - 通过一个切换文字的例子,亲手实践了 StatefulWidget
多动手敲一敲上面的例子,试着把点击区域改成按钮,或者在切换文字的同时改变背景颜色。下一篇我们将进入布局的世界,看看如何用 Row 和 Column 组合出丰富界面。