Flutter状态管理篇之ChangeNotifier基础篇(一)

目录

前言

一、什么是ChangeNotifier

[二、ChangeNotifier 的基本用法](#二、ChangeNotifier 的基本用法)

[三、结合Flutter UI 使用](#三、结合Flutter UI 使用)

[四、结合 Provider 的高级用法](#四、结合 Provider 的高级用法)

[五、ChangeNotifier 的优势与注意事项](#五、ChangeNotifier 的优势与注意事项)

[5.1 优势](#5.1 优势)

[5.2 注意事项](#5.2 注意事项)

[六、与 ValueNotifier 的比较](#六、与 ValueNotifier 的比较)

七、实际应用场景

八、总结


前言

在 Flutter 开发中,ChangeNotifier 是一个强大的状态管理工具,位于 package:flutter/foundation.dart 中,广泛用于实现响应式 UI 更新。它通过提供监听者管理和通知机制,帮助开发者在状态变化时通知相关的 UI 组件。

本文将详细介绍ChangeNotifier 的用法,包括其基本概念、实现方式、实际应用场景,以及与 Provider 等工具的结合方式。

一、什么是ChangeNotifier

ChangeNotifier 是一个 mixin 类,实现了 Listenable 接口,用于管理状态并通知注册的监听者(VoidCallback 类型)。它允许开发者定义复杂的状态逻辑,并在状态变化时通过 notifyListeners 方法触发 UI 更新。ChangeNotifier 是 Flutter 响应式编程的核心,适合需要管理多字段状态或自定义通知逻辑的场景。

ChangeNotifier的核心特征如下:

  1. 监听者管理:通过 addListenerremoveListener 管理监听者列表。
  2. 通知机制:通过 notifyListeners 通知所有监听者状态变化。
  3. 销毁支持:提供 dispose 方法清理资源,防止内存泄漏。
  4. 调试友好:内置调试支持(如 debugAssertNotDisposed),帮助发现错误。

二、ChangeNotifier 的基本用法

ChangeNotifier通常通过mixin的方式混入自定义类,用于管理状态。

以下是一个简单的计数器示例,展示其基本用法:

图1.计时器实例

复制代码
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(
      title: 'Flutter Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
        useMaterial3: true,
      ),
      home: const MyHomePage(title: 'ChangeNotifier 用法示例'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});
  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  final counter = Counter();

  void listener() {
    setState(() {}); // 触发 UI 更新
  }

  @override
  void initState() {
    super.initState();
    counter.addListener(listener);
  }

  @override
  void dispose() {
    counter.removeListener(listener);
    counter.dispose(); // 清理 Counter 资源
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
        backgroundColor: Theme.of(context).colorScheme.primaryContainer,
      ),
      body: Center(
        child: Padding(
          padding: const EdgeInsets.all(16.0),
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Text(
                '按钮点击次数',
                style: Theme.of(context).textTheme.headlineSmall?.copyWith(
                  fontWeight: FontWeight.bold,
                  color: Theme.of(context).colorScheme.primary,
                ),
              ),
              const SizedBox(height: 20),
              Card(
                elevation: 8,
                shape: RoundedRectangleBorder(
                  borderRadius: BorderRadius.circular(12),
                ),
                color: Theme.of(context).colorScheme.surfaceVariant,
                child: Padding(
                  padding: const EdgeInsets.symmetric(vertical: 40, horizontal: 60),
                  child: Text(
                    '${counter.count}',
                    style: Theme.of(context).textTheme.displayMedium?.copyWith(
                      fontWeight: FontWeight.bold,
                      color: Theme.of(context).colorScheme.onSurfaceVariant,
                    ),
                  ),
                ),
              ),
              const SizedBox(height: 30),
              Row(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  ElevatedButton.icon(
                    onPressed: counter.decrement, // 直接调用方法,无需 setState
                    icon: const Icon(Icons.remove),
                    label: const Text('减少'),
                    style: ElevatedButton.styleFrom(
                      padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
                    ),
                  ),
                  const SizedBox(width: 20),
                  ElevatedButton.icon(
                    onPressed: counter.increment, // 直接调用方法,无需 setState
                    icon: const Icon(Icons.add),
                    label: const Text('增加'),
                    style: ElevatedButton.styleFrom(
                      padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
                    ),
                  ),
                ],
              ),
            ],
          ),
        ),
      ),
    );
  }
}

class Counter with ChangeNotifier {
  int _count = 0;

  int get count => _count;

  void increment() {
    _count++;
    notifyListeners();
  }

  void decrement() {
    if (_count > 0) { // 防止计数变为负数
      _count--;
      notifyListeners();
    }
  }
  
}
  • 说明
    • Counter 类通过 with ChangeNotifier 混入通知功能。
    • _count 是私有状态,count getter 提供外部访问。
    • incrementdecrement 修改状态后调用 notifyListeners,触发监听者回调。

三、结合Flutter UI 使用

在 Flutter 应用中,ChangeNotifier 通常与 UI 组件结合,通过监听状态变化自动更新界面。以下是一个完整的计数器应用,展示如何在 Flutter 中使用 ChangeNotifier

下面是一个计时器的例子:

复制代码
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.white),
      ),
      home: const MyHomePage(title: 'ChangeNotifier用法'),
    );
  }
}

class MyHomePage extends StatelessWidget {
  const MyHomePage({super.key, required this.title});
  final String title;

  @override
  Widget build(BuildContext context) {
    final counter = context.watch<Counter>();

    return Scaffold(
      appBar: AppBar(
        title: Text(title),
      ),
      body: Center(
        child: Padding(
          padding: const EdgeInsets.all(16.0),
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Text(
                '按钮点击次数',
                style: Theme.of(context).textTheme.titleLarge,
              ),
              const SizedBox(height: 20),
              Card(
                elevation: 8,
                shape: RoundedRectangleBorder(
                  borderRadius: BorderRadius.circular(12),
                ),
                color: Theme.of(context).colorScheme.surfaceVariant,
                child: Padding(
                  padding: const EdgeInsets.symmetric(vertical: 40, horizontal: 60),
                  child: Text(
                    '${counter.count}',
                    style: Theme.of(context).textTheme.displayMedium?.copyWith(
                      color: Theme.of(context).colorScheme.primary,
                      fontWeight: FontWeight.bold,
                    ),
                  ),
                ),
              ),
              const SizedBox(height: 30),
              Row(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  ElevatedButton.icon(
                    onPressed: counter.decrement,
                    icon: const Icon(Icons.minimize),
                    label: const Text('减少'),
                  ),
                  const SizedBox(width: 20),
                  ElevatedButton.icon(
                    onPressed: counter.increment,
                    icon: const Icon(Icons.add),
                    label: const Text('增加'),
                  ),
                ],
              ),
            ],
          ),
        ),
      ),
    );
  }
}

class Counter with ChangeNotifier {
  int _count = 0;

  int get count => _count;

  void increment() {
    _count++;
    notifyListeners();
  }

  void decrement() {
    if (_count > 0) { // 防止计数变为负数
      _count--;
      notifyListeners();
    }
  }
}
  • 依赖 :需要添加 provider 包到 pubspec.yaml

    复制代码
    dependencies:
      provider: ^6.1.5
  • 说明

    • 使用 ChangeNotifierProvider 提供 Counter 实例,注入到 widget 树。
    • Consumer 监听 Counter 的变化,自动重建显示计数的 Text
    • 点击"+"或"-"按钮调用 incrementdecrement,触发 notifyListeners,更新 UI。

四、结合 Provider 的高级用法

ChangeNotifier 常与 provider 包结合,用于更复杂的状态管理。以下是一个管理用户信息的示例,展示多字段状态管理。

用户信息管理:

复制代码
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

class UserModel with ChangeNotifier {
  String _name = 'Anonymous';
  int _age = 0;

  String get name => _name;
  int get age => _age;

  void updateName(String newName) {
    _name = newName;
    notifyListeners();
  }

  void updateAge(int newAge) {
    _age = newAge;
    notifyListeners();
  }
}

void main() {
  runApp(const MyApp());
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'UserModel 示例',
      theme: ThemeData(primarySwatch: Colors.green, useMaterial3: true),
      home: ChangeNotifierProvider(
        create: (_) => UserModel(),
        child: const UserPage(),
      ),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    final userModel = context.read<UserModel>();
    return Scaffold(
      appBar: AppBar(title: const Text('用户信息')),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Consumer<UserModel>(
              builder: (context, user, child) {
                return Text(
                  '姓名: ${user.name}, 年龄: ${user.age}',
                  style: const TextStyle(fontSize: 20),
                );
              },
            ),
            const SizedBox(height: 20),
            TextField(
              decoration: const InputDecoration(labelText: '输入姓名'),
              onChanged: (value) => userModel.updateName(value),
            ),
            const SizedBox(height: 10),
            Row(
              children: [
                ElevatedButton(
                  onPressed: () => userModel.updateAge(userModel.age + 1),
                  child: const Text('年龄 +1'),
                ),
                const SizedBox(width: 10),
                ElevatedButton(
                  onPressed: () => userModel.updateAge(userModel.age - 1),
                  child: const Text('年龄 -1'),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}
  • 说明
    • UserModel 管理 nameage 两个字段。
    • TextField 更新姓名,按钮更新年龄,每次变化调用 notifyListeners
    • Consumer 监听 UserModel 的变化,实时更新 UI。

五、ChangeNotifier 的优势与注意事项

5.1 优势

  1. 灵活性:支持管理复杂状态(多字段、自定义逻辑)。
  2. 响应式:通过 notifyListeners 触发 UI 更新,与 Provider 等工具无缝集成。
  3. 调试支持:内置 debugAssertNotDisposed,防止销毁后误用。
  4. 可扩展:通过 mixin 方式,易于扩展到自定义类。

5.2 注意事项

  1. 手动调用 notifyListeners:开发者需明确在状态变化时调用 notifyListeners,否则 UI 不会更新。

  2. 清理资源:在页面销毁时调用 dispose,避免内存泄漏。例如:

    复制代码
    class Counter with ChangeNotifier {
      @override
      void dispose() {
        super.dispose(); // 必须调用父类的 dispose
      }
    }
  3. 避免递归通知:在 notifyListeners 期间移除监听者或调用 dispose 可能导致错误,需小心处理。

  4. 性能优化:避免频繁调用 notifyListeners,必要时检查状态是否真正变化。

六、与 ValueNotifier 的比较

ChangeNotifierValueNotifier 都用于状态管理,但适用场景不同:

  • ChangeNotifier
    • 适合复杂状态管理(多字段、自定义通知逻辑)。
    • 需手动调用 notifyListeners
    • 更通用,但实现稍复杂。
  • ValueNotifier
    • 专注于单一值管理,自动在值变化时通知。
    • 更轻量,适合简单场景(如计数器、开关)。
    • 继承自 ChangeNotifier,API 更简单。

选择建议

  • 如果只需要管理单一值,使用 ValueNotifier(结合 ValueListenableBuilder)。
  • 如果需要管理多个字段或复杂逻辑,使用 ChangeNotifier(结合 Provider)。

七、实际应用场景

  1. 表单管理:管理多个输入字段的状态(如登录表单)。
  2. 复杂 UI 状态:如购物车、用户设置等需要多字段更新的场景。
  3. 与 Provider 结合:构建大型应用的全局状态管理。
  4. 动画控制:结合 ChangeNotifier 实现自定义动画状态。

八、总结

ChangeNotifier 是 Flutter 中强大的状态管理工具,通过监听者机制实现响应式 UI 更新。它适合管理复杂状态,结合 ProviderConsumer 可以轻松集成到 Flutter 应用中。通过合理使用 notifyListenersdispose,开发者可以构建高效、可维护的状态管理逻辑。相比 ValueNotifierChangeNotifier 提供更大的灵活性,适合需要自定义通知逻辑的场景。希望本文能帮助你快速上手 ChangeNotifier,并在实际项目中灵活运用!

相关推荐
MoFe113 分钟前
【.net core】支持通过属性名称索引的泛型包装类
java·开发语言·.netcore
小白的程序空间19 分钟前
Anaconda Prompt中删除库虚拟环境【保姆级教程】
linux·开发语言·python
tanyongxi6621 分钟前
从零手写红黑树(C++实现详解)
开发语言·数据结构·c++·算法
basketball61632 分钟前
Linux C 信号操作
linux·c语言·开发语言
LinXunFeng35 分钟前
Flutter - 聊天面板库动画生硬?这次让你丝滑个够
前端·flutter·github
PythonicCC1 小时前
Python高级数据类型:字典(Dictionary)
开发语言·python
Kiri霧1 小时前
Kotlin属性重写
android·开发语言·kotlin
皮卡蛋炒饭.2 小时前
初识C++——开启新旅途
开发语言·c++
菜还不练就废了2 小时前
7.19 Java基础 | 异常
java·开发语言
GalaxyPokemon2 小时前
全局变量与局部变量的对比
开发语言·c++