Flutter 小白入门教程Flutter 小白入门教程
首页
学习指南
项目实战
Flutter 官网
编程指南
首页
学习指南
项目实战
Flutter 官网
编程指南
  • 入门基础

    • 📚 基础教程
    • 第1章 - 认识 Flutter
    • 第2章 - 环境搭建
    • 第3章 - Dart 语言基础
    • 第4章 - 第一个 Flutter 应用
    • 第5章 - Widget 基础
    • 第6章 - 布局系统
    • 第7章 - 状态管理入门
    • 第8章 - 页面导航
    • 第9章 - 资源管理
  • 进阶开发

    • 第10章 - 网络请求
    • 第11章 - 本地存储
    • 第12章 - 对话框与反馈
    • 第13章 - 列表进阶
    • 第14章 - 主题定制
    • 第15章 - 状态管理进阶
    • 第16章 - 动画入门
    • 第17章 - 常用第三方包
  • 调试与发布

    • 第18章 - 调试与性能优化
    • 第19章 - 打包与发布
  • 附录

    • 附录A - UI 框架与组件库推荐
    • 附录B - 项目结构最佳实践
    • 附录C - 国际化配置
    • 附录D - 权限处理

第7章 - 状态管理入门

嗨,朋友!我是长安。

前面我们学会了用 Widget 搭建界面,但界面只是静态的。要让应用动起来,就需要管理状态(State)。这一章,我们来学习 Flutter 中最基础的状态管理方法。

🤔 什么是状态?

状态(State)就是应用中会变化的数据。

举几个例子:

  • 计数器的数字:0 → 1 → 2 → 3...
  • 登录状态:未登录 → 已登录
  • 购物车商品数量:0 → 1 → 2...
  • 表单输入内容:'' → 'H' → 'He' → 'Hello'

当状态变化时,UI 需要跟着更新,这就是状态管理要解决的问题。

📦 setState - 最基础的状态管理

setState 是 Flutter 中最简单的状态管理方式,适合组件内部的简单状态。

基本用法

class CounterPage extends StatefulWidget {
  const CounterPage({super.key});

  @override
  State<CounterPage> createState() => _CounterPageState();
}

class _CounterPageState extends State<CounterPage> {
  // 定义状态
  int _count = 0;

  // 更新状态的方法
  void _increment() {
    setState(() {
      _count++;  // 在 setState 中修改状态
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('计数器')),
      body: Center(
        child: Text(
          '$_count',
          style: const TextStyle(fontSize: 60),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _increment,
        child: const Icon(Icons.add),
      ),
    );
  }
}

setState 的工作原理

用户点击按钮
      ↓
调用 _increment()
      ↓
setState(() { _count++ })
      ↓
Flutter 标记 Widget 需要重建
      ↓
重新执行 build() 方法
      ↓
UI 更新显示新的 _count 值

注意

一定要在 setState 回调函数内部修改状态!直接修改不会触发 UI 更新。

// ❌ 错误:直接修改,UI 不会更新
void _increment() {
  _count++;
}

// ✅ 正确:在 setState 中修改
void _increment() {
  setState(() {
    _count++;
  });
}

🎯 实战:待办事项列表

让我们做一个稍微复杂点的例子:一个可以添加和删除的待办事项列表。

class TodoPage extends StatefulWidget {
  const TodoPage({super.key});

  @override
  State<TodoPage> createState() => _TodoPageState();
}

class _TodoPageState extends State<TodoPage> {
  // 状态:待办事项列表
  final List<String> _todos = [];
  // 输入框控制器
  final TextEditingController _controller = TextEditingController();

  // 添加待办
  void _addTodo() {
    if (_controller.text.trim().isEmpty) return;
    
    setState(() {
      _todos.add(_controller.text.trim());
    });
    _controller.clear();  // 清空输入框
  }

  // 删除待办
  void _removeTodo(int index) {
    setState(() {
      _todos.removeAt(index);
    });
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('待办事项')),
      body: Column(
        children: [
          // 输入区域
          Padding(
            padding: const EdgeInsets.all(16),
            child: Row(
              children: [
                Expanded(
                  child: TextField(
                    controller: _controller,
                    decoration: const InputDecoration(
                      hintText: '输入待办事项...',
                      border: OutlineInputBorder(),
                    ),
                    onSubmitted: (_) => _addTodo(),
                  ),
                ),
                const SizedBox(width: 10),
                ElevatedButton(
                  onPressed: _addTodo,
                  child: const Text('添加'),
                ),
              ],
            ),
          ),
          // 列表区域
          Expanded(
            child: _todos.isEmpty
                ? const Center(child: Text('暂无待办事项'))
                : ListView.builder(
                    itemCount: _todos.length,
                    itemBuilder: (context, index) {
                      return ListTile(
                        leading: CircleAvatar(child: Text('${index + 1}')),
                        title: Text(_todos[index]),
                        trailing: IconButton(
                          icon: const Icon(Icons.delete, color: Colors.red),
                          onPressed: () => _removeTodo(index),
                        ),
                      );
                    },
                  ),
          ),
        ],
      ),
    );
  }
}

🔄 父子组件间的状态传递

当状态需要在多个组件之间共享时,通常把状态提升到父组件。

状态提升模式

// 父组件:持有状态
class ParentWidget extends StatefulWidget {
  @override
  State<ParentWidget> createState() => _ParentWidgetState();
}

class _ParentWidgetState extends State<ParentWidget> {
  int _count = 0;

  void _increment() {
    setState(() {
      _count++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        // 子组件1:显示数据
        DisplayWidget(count: _count),
        // 子组件2:操作数据
        ButtonWidget(onPressed: _increment),
      ],
    );
  }
}

// 子组件1:只负责显示(无状态)
class DisplayWidget extends StatelessWidget {
  final int count;
  
  const DisplayWidget({super.key, required this.count});

  @override
  Widget build(BuildContext context) {
    return Text('Count: $count', style: const TextStyle(fontSize: 24));
  }
}

// 子组件2:只负责触发事件(无状态)
class ButtonWidget extends StatelessWidget {
  final VoidCallback onPressed;
  
  const ButtonWidget({super.key, required this.onPressed});

  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      onPressed: onPressed,
      child: const Text('增加'),
    );
  }
}

数据流向

        父组件(持有状态)
           ↙        ↘
    状态数据 ↓         ↓ 回调函数
           ↓         ↓
     DisplayWidget  ButtonWidget
      (显示数据)   (触发事件)
                      ↓
                  调用 onPressed
                      ↓
                  父组件 setState
                      ↓
                   UI 更新

📝 表单状态管理

表单是常见的状态管理场景,Flutter 提供了 Form 和 TextFormField 来简化表单处理。

class LoginForm extends StatefulWidget {
  const LoginForm({super.key});

  @override
  State<LoginForm> createState() => _LoginFormState();
}

class _LoginFormState extends State<LoginForm> {
  final _formKey = GlobalKey<FormState>();
  String _username = '';
  String _password = '';

  void _submit() {
    // 验证表单
    if (_formKey.currentState!.validate()) {
      // 保存表单数据
      _formKey.currentState!.save();
      // 处理登录逻辑
      print('用户名: $_username, 密码: $_password');
    }
  }

  @override
  Widget build(BuildContext context) {
    return Form(
      key: _formKey,
      child: Column(
        children: [
          TextFormField(
            decoration: const InputDecoration(labelText: '用户名'),
            validator: (value) {
              if (value == null || value.isEmpty) {
                return '请输入用户名';
              }
              return null;
            },
            onSaved: (value) => _username = value!,
          ),
          const SizedBox(height: 16),
          TextFormField(
            decoration: const InputDecoration(labelText: '密码'),
            obscureText: true,
            validator: (value) {
              if (value == null || value.length < 6) {
                return '密码至少6位';
              }
              return null;
            },
            onSaved: (value) => _password = value!,
          ),
          const SizedBox(height: 24),
          ElevatedButton(
            onPressed: _submit,
            child: const Text('登录'),
          ),
        ],
      ),
    );
  }
}

🎛️ 复选框和开关状态

class SettingsPage extends StatefulWidget {
  const SettingsPage({super.key});

  @override
  State<SettingsPage> createState() => _SettingsPageState();
}

class _SettingsPageState extends State<SettingsPage> {
  bool _darkMode = false;
  bool _notifications = true;
  bool _autoUpdate = false;

  @override
  Widget build(BuildContext context) {
    return ListView(
      children: [
        SwitchListTile(
          title: const Text('深色模式'),
          subtitle: const Text('使用深色主题'),
          value: _darkMode,
          onChanged: (value) {
            setState(() {
              _darkMode = value;
            });
          },
        ),
        SwitchListTile(
          title: const Text('通知'),
          subtitle: const Text('接收推送通知'),
          value: _notifications,
          onChanged: (value) {
            setState(() {
              _notifications = value;
            });
          },
        ),
        CheckboxListTile(
          title: const Text('自动更新'),
          value: _autoUpdate,
          onChanged: (value) {
            setState(() {
              _autoUpdate = value ?? false;
            });
          },
        ),
      ],
    );
  }
}

🌟 状态管理最佳实践

1. 状态放哪里?

场景建议
仅一个组件使用放在该组件内(setState)
父子组件共享提升到最近的公共父组件
多个页面共享使用状态管理库(下一步)
全局状态使用 Provider/Riverpod 等

2. setState 的注意事项

// ✅ 好的做法:setState 内只修改状态
void _updateName(String name) {
  setState(() {
    _name = name;
  });
}

// ❌ 不好的做法:在 setState 内做复杂计算
void _updateData() {
  setState(() {
    // 不要在这里做网络请求或复杂计算
    _data = someHeavyCalculation();  // 不推荐
  });
}

// ✅ 更好的做法:先计算,再 setState
void _updateData() async {
  final result = await someHeavyCalculation();
  setState(() {
    _data = result;
  });
}

3. 避免不必要的 rebuild

// ❌ 每次 build 都创建新的回调函数
Widget build(BuildContext context) {
  return ElevatedButton(
    onPressed: () => _doSomething(),  // 每次都是新函数
    child: Text('点击'),
  );
}

// ✅ 使用方法引用或提前定义
Widget build(BuildContext context) {
  return ElevatedButton(
    onPressed: _doSomething,  // 方法引用
    child: Text('点击'),
  );
}

📝 小结

这一章我们学习了:

  • ✅ 什么是状态(State)
  • ✅ setState 的使用方法和原理
  • ✅ 实战:待办事项列表
  • ✅ 父子组件间的状态传递(状态提升)
  • ✅ 表单状态管理
  • ✅ 状态管理最佳实践

进阶提示

setState 适合简单场景。当应用变复杂时,建议学习:

  • Provider - 官方推荐的简单状态管理
  • Riverpod - Provider 的进化版
  • Bloc - 适合大型项目
  • GetX - 简单易用

💪 练习题

  1. 创建一个带计数功能的购物车组件(显示商品数量,可增减)
  2. 实现一个主题切换功能(在亮色和暗色主题之间切换)
  3. 创建一个简单的评分组件(5颗星,点击变色)
点击查看答案

练习1:购物车组件

class CartItem extends StatefulWidget {
  const CartItem({super.key});

  @override
  State<CartItem> createState() => _CartItemState();
}

class _CartItemState extends State<CartItem> {
  int _quantity = 1;

  @override
  Widget build(BuildContext context) {
    return Row(
      mainAxisSize: MainAxisSize.min,
      children: [
        IconButton(
          onPressed: _quantity > 1
              ? () => setState(() => _quantity--)
              : null,
          icon: const Icon(Icons.remove),
        ),
        Text('$_quantity', style: const TextStyle(fontSize: 20)),
        IconButton(
          onPressed: () => setState(() => _quantity++),
          icon: const Icon(Icons.add),
        ),
      ],
    );
  }
}

练习3:评分组件

class RatingStars extends StatefulWidget {
  const RatingStars({super.key});

  @override
  State<RatingStars> createState() => _RatingStarsState();
}

class _RatingStarsState extends State<RatingStars> {
  int _rating = 0;

  @override
  Widget build(BuildContext context) {
    return Row(
      mainAxisSize: MainAxisSize.min,
      children: List.generate(5, (index) {
        return IconButton(
          onPressed: () => setState(() => _rating = index + 1),
          icon: Icon(
            index < _rating ? Icons.star : Icons.star_border,
            color: Colors.amber,
            size: 32,
          ),
        );
      }),
    );
  }
}

🚀 下一步

状态管理掌握后,下一章我们来学习 页面导航,让应用有多个页面!


由 编程指南 提供

最近更新: 2026/2/3 16:24
Contributors: 王长安
Prev
第6章 - 布局系统
Next
第8章 - 页面导航