Flutter 学习笔记 (2):深入理解 Widget —— 无状态与有状态组件

4 阅读4分钟

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. 两者对比总结

StatelessWidgetStatefulWidget
状态无内部可变状态拥有内部可变状态(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. 下一步计划

  • 学习常用的布局组件:RowColumnStackContainer
  • 了解状态管理的演变(setState → Provider → Riverpod
  • 尝试写一个简单的待办事项列表(加深对 StatefulWidget 的理解)

8. 本篇小结

本篇博客我们学习了:

  • 什么是 Widget,以及 Flutter 中“一切皆 Widget”的思想
  • 无状态组件(StatelessWidget)和有状态组件(StatefulWidget)的区别
  • 如何用 setState 让界面动起来
  • 通过一个切换文字的例子,亲手实践了 StatefulWidget

多动手敲一敲上面的例子,试着把点击区域改成按钮,或者在切换文字的同时改变背景颜色。下一篇我们将进入布局的世界,看看如何用 Row 和 Column 组合出丰富界面。