Skip to content

第7章:Flutter 状态管理

7.1 状态管理的概念

什么是状态?

在 Flutter 中,状态是指应用中需要存储和管理的数据,这些数据会影响 UI 的显示。

常见的状态类型

  • 局部状态:只影响单个组件的状态,如按钮的点击状态、输入框的内容
  • 全局状态:影响多个组件的状态,如用户登录状态、应用主题设置
  • 临时状态:只在当前会话中存在的状态,如页面滚动位置
  • 持久化状态:需要保存到本地存储的状态,如用户偏好设置、购物车内容

为什么需要状态管理?

当应用变得复杂时,状态管理变得至关重要:

  • 组件间通信:不同组件之间需要共享和同步状态
  • 状态一致性:确保应用中的状态保持一致
  • 代码可维护性:集中管理状态,使代码更易于理解和维护
  • 性能优化:避免不必要的重建和渲染

7.2 基础状态管理(StatefulWidget 自身状态管理)

setState() 方法

在 StatefulWidget 中,使用 setState() 方法来更新状态并触发 UI 刷新:

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

  @override
  State<CounterWidget> createState() => _CounterWidgetState();
}

class _CounterWidgetState extends State<CounterWidget> {
  int _count = 0; // 状态变量

  void _increment() {
    setState(() {
      _count++; // 更新状态
    });
  }

  void _decrement() {
    setState(() {
      if (_count > 0) {
        _count--;
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Text('Count: $_count'),
        Row(
          children: [
            ElevatedButton(onPressed: _decrement, child: const Text('-')),
            const SizedBox(width: 16),
            ElevatedButton(onPressed: _increment, child: const Text('+')),
          ],
        ),
      ],
    );
  }
}

状态传递(父组件向子组件)

通过构造函数将状态从父组件传递给子组件:

dart
// 子组件
class DisplayWidget extends StatelessWidget {
  final int count;
  
  const DisplayWidget({super.key, required this.count});

  @override
  Widget build(BuildContext context) {
    return Text('Current count: $count');
  }
}

// 父组件
class ParentWidget extends StatefulWidget {
  const ParentWidget({super.key});

  @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: [
        DisplayWidget(count: _count), // 传递状态
        ElevatedButton(onPressed: _increment, child: const Text('Increment')),
      ],
    );
  }
}

7.3 父子组件状态通信

子组件向父组件传递状态(回调函数)

通过回调函数将子组件的状态传递给父组件:

dart
// 子组件
class InputWidget extends StatelessWidget {
  final Function(String) onTextChanged;
  
  const InputWidget({super.key, required this.onTextChanged});

  @override
  Widget build(BuildContext context) {
    return TextField(
      onChanged: onTextChanged, // 回调函数
      decoration: const InputDecoration(
        labelText: 'Enter text',
      ),
    );
  }
}

// 父组件
class ParentWidget extends StatefulWidget {
  const ParentWidget({super.key});

  @override
  State<ParentWidget> createState() => _ParentWidgetState();
}

class _ParentWidgetState extends State<ParentWidget> {
  String _text = '';

  void _handleTextChanged(String text) {
    setState(() {
      _text = text;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        InputWidget(onTextChanged: _handleTextChanged),
        Text('You entered: $_text'),
      ],
    );
  }
}

父子组件双向通信

结合 setState 和回调函数实现双向通信:

dart
// 子组件
class CounterButton extends StatelessWidget {
  final int count;
  final Function(int) onCountChanged;
  
  const CounterButton({
    super.key,
    required this.count,
    required this.onCountChanged,
  });

  @override
  Widget build(BuildContext context) {
    return Row(
      children: [
        ElevatedButton(
          onPressed: () => onCountChanged(count - 1),
          child: const Text('-'),
        ),
        const SizedBox(width: 16),
        Text('$count'),
        const SizedBox(width: 16),
        ElevatedButton(
          onPressed: () => onCountChanged(count + 1),
          child: const Text('+'),
        ),
      ],
    );
  }
}

// 父组件
class ParentWidget extends StatefulWidget {
  const ParentWidget({super.key});

  @override
  State<ParentWidget> createState() => _ParentWidgetState();
}

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

  void _updateCount(int newCount) {
    setState(() {
      _count = newCount;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Text('Parent count: $_count'),
        CounterButton(
          count: _count,
          onCountChanged: _updateCount,
        ),
      ],
    );
  }
}

7.4 跨组件状态管理(Provider)

Provider 简介

Provider 是 Flutter 官方推荐的状态管理方案,简单易用,适合中小型应用。

安装

yaml
dependencies:
  provider: ^6.1.1

基本使用步骤

  1. 定义状态类:继承 ChangeNotifier
  2. 提供状态:使用 ChangeNotifierProvider
  3. 消费状态:使用 ConsumerProvider.of

示例:计数器应用

步骤 1:定义状态类

dart
import 'package:flutter/material.dart';

class CounterModel extends ChangeNotifier {
  int _count = 0;

  int get count => _count;

  void increment() {
    _count++;
    notifyListeners(); // 通知监听器
  }

  void decrement() {
    if (_count > 0) {
      _count--;
      notifyListeners();
    }
  }

  void reset() {
    _count = 0;
    notifyListeners();
  }
}

步骤 2:提供状态

在应用根组件中提供状态:

dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'counter_model.dart';

void main() {
  runApp(
    ChangeNotifierProvider(
      create: (context) => CounterModel(),
      child: const MyApp(),
    ),
  );
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(),
    );
  }
}

步骤 3:消费状态

使用 Consumer 消费状态:

dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'counter_model.dart';

class MyHomePage extends StatelessWidget {
  const MyHomePage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Counter App'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Text('You have pushed the button this many times:'),
            Consumer<CounterModel>(
              builder: (context, counter, child) {
                return Text(
                  '${counter.count}',
                  style: Theme.of(context).textTheme.headlineMedium,
                );
              },
            ),
          ],
        ),
      ),
      floatingActionButton: Column(
        mainAxisAlignment: MainAxisAlignment.end,
        children: [
          FloatingActionButton(
            onPressed: () {
              Provider.of<CounterModel>(context, listen: false).increment();
            },
            tooltip: 'Increment',
            child: const Icon(Icons.add),
          ),
          const SizedBox(height: 10),
          FloatingActionButton(
            onPressed: () {
              Provider.of<CounterModel>(context, listen: false).decrement();
            },
            tooltip: 'Decrement',
            child: const Icon(Icons.remove),
          ),
          const SizedBox(height: 10),
          FloatingActionButton(
            onPressed: () {
              Provider.of<CounterModel>(context, listen: false).reset();
            },
            tooltip: 'Reset',
            child: const Icon(Icons.refresh),
          ),
        ],
      ),
    );
  }
}

多个状态管理

使用 MultiProvider 管理多个状态:

dart
void main() {
  runApp(
    MultiProvider(
      providers: [
        ChangeNotifierProvider(create: (context) => CounterModel()),
        ChangeNotifierProvider(create: (context) => ThemeModel()),
        ChangeNotifierProvider(create: (context) => UserModel()),
      ],
      child: const MyApp(),
    ),
  );
}

7.5 其他状态管理方案

GetX

GetX 是一个轻量级的状态管理方案,简洁高效,新手友好。

安装

yaml
dependencies:
  get: ^4.6.6

使用

dart
import 'package:get/get.dart';

class CounterController extends GetxController {
  var count = 0.obs;

  void increment() => count++;
  void decrement() => count > 0 ? count-- : null;
  void reset() => count.value = 0;
}

// 提供状态
void main() {
  runApp(GetMaterialApp(
    home: HomePage(),
  ));
}

// 消费状态
class HomePage extends StatelessWidget {
  final controller = Get.put(CounterController());

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('GetX Counter')),
      body: Center(
        child: Obx(() => Text('Count: ${controller.count}')),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: controller.increment,
        child: Icon(Icons.add),
      ),
    );
  }
}

Bloc

Bloc 是一个基于流的状态管理方案,适合复杂项目,规范性强。

安装

yaml
dependencies:
  flutter_bloc: ^8.1.3
  bloc: ^8.1.2

使用

dart
// 事件
abstract class CounterEvent {} 
class IncrementEvent extends CounterEvent {} 
class DecrementEvent extends CounterEvent {} 

// 状态
class CounterState {
  final int count;
  CounterState(this.count);
}

// Bloc
class CounterBloc extends Bloc<CounterEvent, CounterState> {
  CounterBloc() : super(CounterState(0)) {
    on<IncrementEvent>((event, emit) {
      emit(CounterState(state.count + 1));
    });
    on<DecrementEvent>((event, emit) {
      if (state.count > 0) {
        emit(CounterState(state.count - 1));
      }
    });
  }
}

// 使用
class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return BlocProvider(
      create: (context) => CounterBloc(),
      child: BlocBuilder<CounterBloc, CounterState>(
        builder: (context, state) {
          return Scaffold(
            appBar: AppBar(title: Text('Bloc Counter')),
            body: Center(child: Text('Count: ${state.count}')),
            floatingActionButton: FloatingActionButton(
              onPressed: () => context.read<CounterBloc>().add(IncrementEvent()),
              child: Icon(Icons.add),
            ),
          );
        },
      ),
    );
  }
}

7.6 实操案例:主题切换应用

目标

使用 Provider 实现主题切换功能,让用户可以在亮色和暗色主题之间切换。

步骤 1:创建主题模型

dart
import 'package:flutter/material.dart';

class ThemeModel extends ChangeNotifier {
  bool _isDarkMode = false;

  bool get isDarkMode => _isDarkMode;

  ThemeData get themeData => _isDarkMode ? darkTheme : lightTheme;

  void toggleTheme() {
    _isDarkMode = !_isDarkMode;
    notifyListeners();
  }

  static final lightTheme = ThemeData(
    brightness: Brightness.light,
    primarySwatch: Colors.blue,
    backgroundColor: Colors.white,
    textTheme: const TextTheme(
      bodyLarge: TextStyle(color: Colors.black),
    ),
  );

  static final darkTheme = ThemeData(
    brightness: Brightness.dark,
    primarySwatch: Colors.blue,
    backgroundColor: Colors.grey[900],
    textTheme: const TextTheme(
      bodyLarge: TextStyle(color: Colors.white),
    ),
  );
}

步骤 2:提供主题状态

dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'theme_model.dart';
import 'home_page.dart';

void main() {
  runApp(
    ChangeNotifierProvider(
      create: (context) => ThemeModel(),
      child: const MyApp(),
    ),
  );
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return Consumer<ThemeModel>(
      builder: (context, themeModel, child) {
        return MaterialApp(
          title: 'Theme Switcher',
          theme: themeModel.themeData,
          home: const HomePage(),
        );
      },
    );
  }
}

步骤 3:创建首页

dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'theme_model.dart';

class HomePage extends StatelessWidget {
  const HomePage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Theme Switcher'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Text(
              'Hello, Flutter!',
              style: TextStyle(fontSize: 24),
            ),
            const SizedBox(height: 40),
            Consumer<ThemeModel>(
              builder: (context, themeModel, child) {
                return Text(
                  'Current theme: ${themeModel.isDarkMode ? 'Dark' : 'Light'}',
                  style: const TextStyle(fontSize: 18),
                );
              },
            ),
            const SizedBox(height: 40),
            ElevatedButton(
              onPressed: () {
                Provider.of<ThemeModel>(context, listen: false).toggleTheme();
              },
              child: const Text('Toggle Theme'),
            ),
          ],
        ),
      ),
    );
  }
}

步骤 4:运行应用

  1. 启动模拟器或连接真机
  2. 运行项目
  3. 点击 "Toggle Theme" 按钮,观察主题变化

7.7 新手易错点

状态更新不调用 setState()

错误:直接修改状态变量,UI 不更新。

解决方案

  • 必须在 setState() 回调中修改状态
  • 对于 Provider,必须调用 notifyListeners()

跨组件状态传递错误

错误:使用全局变量或静态变量来共享状态。

解决方案

  • 使用 Provider、GetX 等状态管理方案
  • 使用回调函数进行组件间通信
  • 避免使用全局变量,难以维护

Provider 使用错误

错误

  • 忘记在根组件提供状态
  • build 方法中创建 Provider
  • 错误使用 listen 参数

解决方案

  • 在应用根组件使用 ChangeNotifierProvider
  • 不要在 build 方法中创建 Provider 实例
  • 读取状态时 listen: true,修改状态时 listen: false

状态管理方案选择错误

错误:选择过于复杂的状态管理方案。

解决方案

  • 小型应用:使用 setState 或 Provider
  • 中型应用:使用 Provider 或 GetX
  • 大型应用:使用 Bloc 或 Riverpod

性能问题

错误

  • 频繁调用 setState
  • 不必要的重建
  • 状态管理不当导致的性能问题

解决方案

  • 合理使用 const 构造函数
  • 使用 const 修饰符避免不必要的重建
  • 选择合适的状态管理方案
  • 使用 SelectConsumer 优化重建范围

7.8 小结

本章介绍了 Flutter 的状态管理,包括基础状态管理、父子组件状态通信、跨组件状态管理等内容。状态管理是 Flutter 开发中的核心难点,掌握好状态管理对于构建复杂的 Flutter 应用至关重要。

我们学习了几种常见的状态管理方案:

  • setState:适合简单的局部状态管理
  • Provider:官方推荐,适合中小型应用
  • GetX:简洁高效,新手友好
  • Bloc:基于流,适合复杂项目

通过实操案例,我们使用 Provider 实现了主题切换功能,体验了跨组件状态管理的实现。在实际开发中,你应该根据应用的复杂度选择合适的状态管理方案。

在接下来的章节中,我们将学习 Flutter 的路由与导航,掌握如何实现页面之间的跳转和参数传递。

© 2026 编程马·菜鸟教程 版权所有