在这里插入图片描述

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net


一、场景引入:为什么需要轻量级状态管理?

在移动应用开发中,状态管理是核心问题之一。想象一下这样的场景:你正在开发一个购物车功能,用户添加商品时,购物车图标需要实时更新数量;用户切换主题时,整个应用的配色需要立即改变;用户填写表单时,提交按钮的启用状态需要根据表单验证结果动态变化。这些场景都需要一种简单、高效的状态管理方案。

这就是为什么我们需要 ValueNotifierValueNotifier 是 Flutter 提供的轻量级状态管理工具,它继承自 ChangeNotifier,专门用于管理单一值的状态变化,非常适合简单的状态管理场景。

📱 1.1 状态管理的典型应用场景

在现代移动应用中,状态管理的需求非常广泛:

表单状态管理:用户填写表单时,需要实时验证输入内容,根据验证结果更新错误提示、按钮状态等。使用 ValueNotifier 可以轻松管理表单的验证状态。

购物车管理:用户添加或删除商品时,购物车数量、总价需要实时更新。ValueNotifier 可以高效地管理这些简单的数值状态。

主题切换:用户切换深色/浅色主题时,整个应用的配色需要立即改变。ValueNotifier 配合 ValueListenableBuilder 可以实现优雅的主题切换。

计数器功能:简单的计数器应用,用户点击按钮增加或减少数值。这是 ValueNotifier 最经典的用例。

开关状态:各种开关、复选框、单选按钮的状态管理,如设置页面中的各种选项开关。

1.2 ValueNotifier 与其他状态管理方案对比

Flutter 提供了多种状态管理方案:

方案 适用场景 学习成本 性能 代码量
setState 简单局部状态
ValueNotifier 单一值状态
ChangeNotifier 复杂对象状态
InheritedWidget 跨组件共享
Provider 全局状态管理
Riverpod 现代状态管理
BLoC/Cubit 企业级状态管理

对于单一值状态管理场景,ValueNotifier 是最佳选择:

简单易用:只需要创建一个 ValueNotifier 实例,通过 value 属性读写值,通过 addListener 监听变化。

性能优秀:ValueNotifier 只在值真正变化时才通知监听器,避免不必要的重建。

内存友好:相比复杂的状态管理方案,ValueNotifier 的内存占用极小。

与 Flutter 完美集成:配合 ValueListenableBuilder 使用,可以实现高效的局部刷新。

1.3 ValueNotifier 核心概念

理解 ValueNotifier 的核心概念是掌握状态管理的关键:

ValueNotifier:继承自 ChangeNotifier 的简单类,专门用于管理单一值。它会在 value 属性被设置为新值时通知所有监听器。

ValueListenableBuilder:Flutter 提供的组件,用于监听 ValueListenable(ValueNotifier 实现了该接口)的变化,并在值变化时重建子组件。

ValueListenable:一个接口,定义了获取当前值和监听值变化的能力。ValueNotifier 实现了这个接口。

addListener/removeListener:用于添加和移除监听器的方法。通常不需要直接使用,因为 ValueListenableBuilder 会自动管理。


二、技术架构设计

在正式编写代码之前,我们需要设计一个清晰的架构。良好的架构设计可以让代码更易于理解、维护和扩展。

🏛️ 2.1 状态管理架构设计

┌─────────────────────────────────────────────────────────────┐
│                    状态层                                    │
│  ┌─────────────────────────────────────────────────────┐    │
│  │           ValueNotifier<T>                          │    │
│  │  - 存储单一值 value                                  │    │
│  │  - 值变化时通知监听器                                │    │
│  │  - 提供 value getter/setter                         │    │
│  └─────────────────────────────────────────────────────┘    │
│                              │                               │
│                              │ 通知                          │
│                              ▼                               │
│  ┌─────────────────────────────────────────────────────┐    │
│  │           Listeners                                 │    │
│  │  - ValueListenableBuilder 自动监听                   │    │
│  │  - 手动 addListener 监听                             │    │
│  └─────────────────────────────────────────────────────┘    │
└─────────────────────────────────────────────────────────────┘
                               │
                               │ 重建
                               ▼
┌─────────────────────────────────────────────────────────────┐
│                    UI 层                                     │
│  ┌─────────────────────────────────────────────────────┐    │
│  │           ValueListenableBuilder<T>                 │    │
│  │  - 监听 ValueListenable 变化                        │    │
│  │  - 自动管理监听器生命周期                            │    │
│  │  - 只重建 builder 中的内容                          │    │
│  └─────────────────────────────────────────────────────┘    │
│                              │                               │
│                              ▼                               │
│  ┌─────────────────────────────────────────────────────┐    │
│  │           Widget Tree                               │    │
│  │  - 根据最新值构建 UI                                 │    │
│  └─────────────────────────────────────────────────────┘    │
└─────────────────────────────────────────────────────────────┘

🎯 2.2 ValueNotifier 生命周期

创建 ValueNotifier
      │
      ▼
设置初始值 value
      │
      ▼
ValueListenableBuilder 开始监听
      │
      ├──▶ 读取 value: 获取当前值
      │
      ├──▶ 设置 value = newValue
      │    │
      │    └──▶ 值变化?通知监听器 → 重建 UI
      │
      └──▶ dispose() 释放资源
            │
            └──▶ 移除所有监听器

📐 2.3 ValueNotifier vs ChangeNotifier

特性 ValueNotifier ChangeNotifier
存储值 单一值 需要自定义属性
通知时机 值变化时自动通知 需要手动调用 notifyListeners
使用场景 简单状态 复杂状态对象
代码量
性能 高(自动去重) 中(每次都通知)

三、核心功能实现

🔧 3.1 基础 ValueNotifier 使用

import 'package:flutter/material.dart';

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

  
  State<BasicValueNotifierPage> createState() => _BasicValueNotifierPageState();
}

class _BasicValueNotifierPageState extends State<BasicValueNotifierPage> {
  // 创建 ValueNotifier
  final ValueNotifier<int> _counter = ValueNotifier<int>(0);
  
  
  void dispose() {
    // 释放资源
    _counter.dispose();
    super.dispose();
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('基础 ValueNotifier')),
      body: Center(
        // 使用 ValueListenableBuilder 监听变化
        child: ValueListenableBuilder<int>(
          valueListenable: _counter,
          builder: (context, value, child) {
            return Text(
              '计数: $value',
              style: const TextStyle(fontSize: 32),
            );
          },
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          // 修改值,自动通知监听器
          _counter.value++;
        },
        child: const Icon(Icons.add),
      ),
    );
  }
}

📝 3.2 表单验证实现

/// 表单验证管理器
class FormValidationManager {
  final ValueNotifier<String> email = ValueNotifier('');
  final ValueNotifier<String> password = ValueNotifier('');
  final ValueNotifier<bool> isValid = ValueNotifier(false);
  final ValueNotifier<String?> emailError = ValueNotifier(null);
  final ValueNotifier<String?> passwordError = ValueNotifier(null);
  
  FormValidationManager() {
    email.addListener(_validate);
    password.addListener(_validate);
  }
  
  void _validate() {
    final emailValue = email.value;
    final passwordValue = password.value;
    
    // 验证邮箱
    if (emailValue.isEmpty) {
      emailError.value = null;
    } else if (!_isValidEmail(emailValue)) {
      emailError.value = '请输入有效的邮箱地址';
    } else {
      emailError.value = null;
    }
    
    // 验证密码
    if (passwordValue.isEmpty) {
      passwordError.value = null;
    } else if (passwordValue.length < 6) {
      passwordError.value = '密码至少需要6个字符';
    } else {
      passwordError.value = null;
    }
    
    // 更新整体验证状态
    isValid.value = _isValidEmail(emailValue) && passwordValue.length >= 6;
  }
  
  bool _isValidEmail(String email) {
    return RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$').hasMatch(email);
  }
  
  void dispose() {
    email.dispose();
    password.dispose();
    isValid.dispose();
    emailError.dispose();
    passwordError.dispose();
  }
}

/// 表单验证页面
class FormValidationPage extends StatefulWidget {
  const FormValidationPage({super.key});

  
  State<FormValidationPage> createState() => _FormValidationPageState();
}

class _FormValidationPageState extends State<FormValidationPage> {
  final FormValidationManager _manager = FormValidationManager();
  final TextEditingController _emailController = TextEditingController();
  final TextEditingController _passwordController = TextEditingController();
  
  
  void initState() {
    super.initState();
    _emailController.addListener(() {
      _manager.email.value = _emailController.text;
    });
    _passwordController.addListener(() {
      _manager.password.value = _passwordController.text;
    });
  }
  
  
  void dispose() {
    _manager.dispose();
    _emailController.dispose();
    _passwordController.dispose();
    super.dispose();
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('表单验证')),
      body: Padding(
        padding: const EdgeInsets.all(24),
        child: Column(
          children: [
            // 邮箱输入框
            ValueListenableBuilder<String?>(
              valueListenable: _manager.emailError,
              builder: (context, error, child) {
                return TextField(
                  controller: _emailController,
                  decoration: InputDecoration(
                    labelText: '邮箱',
                    errorText: error,
                    border: const OutlineInputBorder(),
                  ),
                  keyboardType: TextInputType.emailAddress,
                );
              },
            ),
            const SizedBox(height: 16),
            
            // 密码输入框
            ValueListenableBuilder<String?>(
              valueListenable: _manager.passwordError,
              builder: (context, error, child) {
                return TextField(
                  controller: _passwordController,
                  decoration: InputDecoration(
                    labelText: '密码',
                    errorText: error,
                    border: const OutlineInputBorder(),
                  ),
                  obscureText: true,
                );
              },
            ),
            const SizedBox(height: 24),
            
            // 提交按钮
            ValueListenableBuilder<bool>(
              valueListenable: _manager.isValid,
              builder: (context, isValid, child) {
                return SizedBox(
                  width: double.infinity,
                  child: ElevatedButton(
                    onPressed: isValid ? () => _submit() : null,
                    child: const Text('提交'),
                  ),
                );
              },
            ),
          ],
        ),
      ),
    );
  }
  
  void _submit() {
    ScaffoldMessenger.of(context).showSnackBar(
      const SnackBar(content: Text('提交成功!')),
    );
  }
}

🛒 3.3 购物车实现

/// 购物车商品
class CartItem {
  final String id;
  final String name;
  final double price;
  final String image;
  int quantity;
  
  CartItem({
    required this.id,
    required this.name,
    required this.price,
    required this.image,
    this.quantity = 1,
  });
  
  double get total => price * quantity;
}

/// 购物车管理器
class CartManager {
  final ValueNotifier<List<CartItem>> items = ValueNotifier([]);
  final ValueNotifier<int> itemCount = ValueNotifier(0);
  final ValueNotifier<double> totalPrice = ValueNotifier(0.0);
  
  void addItem(CartItem item) {
    final currentItems = List<CartItem>.from(items.value);
    final existingIndex = currentItems.indexWhere((i) => i.id == item.id);
    
    if (existingIndex >= 0) {
      currentItems[existingIndex].quantity++;
    } else {
      currentItems.add(item);
    }
    
    items.value = currentItems;
    _updateTotals();
  }
  
  void removeItem(String id) {
    final currentItems = List<CartItem>.from(items.value);
    currentItems.removeWhere((item) => item.id == id);
    items.value = currentItems;
    _updateTotals();
  }
  
  void updateQuantity(String id, int quantity) {
    final currentItems = List<CartItem>.from(items.value);
    final index = currentItems.indexWhere((i) => i.id == id);
    
    if (index >= 0) {
      if (quantity <= 0) {
        currentItems.removeAt(index);
      } else {
        currentItems[index].quantity = quantity;
      }
      items.value = currentItems;
      _updateTotals();
    }
  }
  
  void clear() {
    items.value = [];
    _updateTotals();
  }
  
  void _updateTotals() {
    itemCount.value = items.value.fold(0, (sum, item) => sum + item.quantity);
    totalPrice.value = items.value.fold(0.0, (sum, item) => sum + item.total);
  }
  
  void dispose() {
    items.dispose();
    itemCount.dispose();
    totalPrice.dispose();
  }
}

/// 购物车页面
class CartPage extends StatelessWidget {
  final CartManager cartManager;
  
  const CartPage({super.key, required this.cartManager});

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('购物车'),
        actions: [
          ValueListenableBuilder<int>(
            valueListenable: cartManager.itemCount,
            builder: (context, count, child) {
              return Stack(
                alignment: Alignment.center,
                children: [
                  const Icon(Icons.shopping_cart),
                  if (count > 0)
                    Positioned(
                      right: 0,
                      top: 8,
                      child: Container(
                        padding: const EdgeInsets.all(4),
                        decoration: const BoxDecoration(
                          color: Colors.red,
                          shape: BoxShape.circle,
                        ),
                        child: Text(
                          count > 99 ? '99+' : count.toString(),
                          style: const TextStyle(
                            color: Colors.white,
                            fontSize: 10,
                          ),
                        ),
                      ),
                    ),
                ],
              );
            },
          ),
          const SizedBox(width: 16),
        ],
      ),
      body: ValueListenableBuilder<List<CartItem>>(
        valueListenable: cartManager.items,
        builder: (context, items, child) {
          if (items.isEmpty) {
            return const Center(child: Text('购物车是空的'));
          }
          
          return Column(
            children: [
              Expanded(
                child: ListView.builder(
                  itemCount: items.length,
                  itemBuilder: (context, index) {
                    return _buildCartItem(items[index]);
                  },
                ),
              ),
              _buildBottomBar(),
            ],
          );
        },
      ),
    );
  }
  
  Widget _buildCartItem(CartItem item) {
    return Card(
      margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
      child: Padding(
        padding: const EdgeInsets.all(12),
        child: Row(
          children: [
            Container(
              width: 60,
              height: 60,
              decoration: BoxDecoration(
                color: Colors.grey.shade200,
                borderRadius: BorderRadius.circular(8),
              ),
              child: const Icon(Icons.image, color: Colors.grey),
            ),
            const SizedBox(width: 12),
            Expanded(
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Text(item.name, style: const TextStyle(fontWeight: FontWeight.w500)),
                  const SizedBox(height: 4),
                  Text(
                    ${item.price.toStringAsFixed(2)}',
                    style: const TextStyle(color: Colors.red),
                  ),
                ],
              ),
            ),
            Row(
              children: [
                IconButton(
                  icon: const Icon(Icons.remove_circle_outline),
                  onPressed: () {
                    cartManager.updateQuantity(item.id, item.quantity - 1);
                  },
                ),
                Text(item.quantity.toString()),
                IconButton(
                  icon: const Icon(Icons.add_circle_outline),
                  onPressed: () {
                    cartManager.updateQuantity(item.id, item.quantity + 1);
                  },
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
  
  Widget _buildBottomBar() {
    return Container(
      padding: const EdgeInsets.all(16),
      decoration: BoxDecoration(
        color: Colors.white,
        boxShadow: [
          BoxShadow(
            color: Colors.black.withOpacity(0.05),
            blurRadius: 4,
            offset: const Offset(0, -2),
          ),
        ],
      ),
      child: SafeArea(
        child: Row(
          children: [
            const Text('合计:', style: TextStyle(fontSize: 16)),
            ValueListenableBuilder<double>(
              valueListenable: cartManager.totalPrice,
              builder: (context, total, child) {
                return Text(
                  ${total.toStringAsFixed(2)}',
                  style: const TextStyle(
                    fontSize: 20,
                    color: Colors.red,
                    fontWeight: FontWeight.bold,
                  ),
                );
              },
            ),
            const Spacer(),
            ElevatedButton(
              onPressed: () {},
              style: ElevatedButton.styleFrom(
                backgroundColor: Colors.red,
                foregroundColor: Colors.white,
              ),
              child: const Text('去结算'),
            ),
          ],
        ),
      ),
    );
  }
}

🎨 3.4 主题切换实现

/// 主题管理器
class ThemeManager {
  final ValueNotifier<ThemeMode> themeMode = ValueNotifier(ThemeMode.system);
  
  void toggleTheme() {
    if (themeMode.value == ThemeMode.light) {
      themeMode.value = ThemeMode.dark;
    } else {
      themeMode.value = ThemeMode.light;
    }
  }
  
  void setTheme(ThemeMode mode) {
    themeMode.value = mode;
  }
  
  void dispose() {
    themeMode.dispose();
  }
}

/// 主题切换应用
class ThemeApp extends StatelessWidget {
  final ThemeManager themeManager = ThemeManager();
  
  ThemeApp({super.key});

  
  Widget build(BuildContext context) {
    return ValueListenableBuilder<ThemeMode>(
      valueListenable: themeManager.themeMode,
      builder: (context, mode, child) {
        return MaterialApp(
          debugShowCheckedModeBanner: false,
          theme: ThemeData(
            colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
            useMaterial3: true,
          ),
          darkTheme: ThemeData(
            colorScheme: ColorScheme.fromSeed(
              seedColor: Colors.blue,
              brightness: Brightness.dark,
            ),
            useMaterial3: true,
          ),
          themeMode: mode,
          home: SettingsPage(themeManager: themeManager),
        );
      },
    );
  }
}

/// 设置页面
class SettingsPage extends StatelessWidget {
  final ThemeManager themeManager;
  
  const SettingsPage({super.key, required this.themeManager});

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('设置')),
      body: ListView(
        children: [
          ValueListenableBuilder<ThemeMode>(
            valueListenable: themeManager.themeMode,
            builder: (context, mode, child) {
              return ListTile(
                leading: Icon(
                  mode == ThemeMode.dark ? Icons.dark_mode : Icons.light_mode,
                ),
                title: const Text('主题模式'),
                subtitle: Text(_getThemeName(mode)),
                trailing: Switch(
                  value: mode == ThemeMode.dark,
                  onChanged: (value) {
                    themeManager.setTheme(value ? ThemeMode.dark : ThemeMode.light);
                  },
                ),
              );
            },
          ),
        ],
      ),
    );
  }
  
  String _getThemeName(ThemeMode mode) {
    switch (mode) {
      case ThemeMode.system:
        return '跟随系统';
      case ThemeMode.light:
        return '浅色模式';
      case ThemeMode.dark:
        return '深色模式';
    }
  }
}

四、完整应用示例

下面是一个完整的计数器应用,展示 ValueNotifier 的各种用法:

import 'package:flutter/material.dart';

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

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

  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'ValueNotifier 示例',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
        useMaterial3: true,
      ),
      home: const HomePage(),
    );
  }
}

/// 计数器管理器
class CounterManager {
  final ValueNotifier<int> count = ValueNotifier(0);
  final ValueNotifier<int> maxCount = ValueNotifier(100);
  final ValueNotifier<int> minCount = ValueNotifier(0);
  final ValueNotifier<bool> canIncrement = ValueNotifier(true);
  final ValueNotifier<bool> canDecrement = ValueNotifier(true);
  
  CounterManager() {
    count.addListener(_updateButtonStates);
  }
  
  void increment() {
    if (count.value < maxCount.value) {
      count.value++;
    }
  }
  
  void decrement() {
    if (count.value > minCount.value) {
      count.value--;
    }
  }
  
  void reset() {
    count.value = 0;
  }
  
  void _updateButtonStates() {
    canIncrement.value = count.value < maxCount.value;
    canDecrement.value = count.value > minCount.value;
  }
  
  void dispose() {
    count.dispose();
    maxCount.dispose();
    minCount.dispose();
    canIncrement.dispose();
    canDecrement.dispose();
  }
}

/// 首页
class HomePage extends StatefulWidget {
  const HomePage({super.key});

  
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  final CounterManager _manager = CounterManager();
  
  
  void dispose() {
    _manager.dispose();
    super.dispose();
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('🔄 ValueNotifier 计数器'),
        centerTitle: true,
        actions: [
          IconButton(
            icon: const Icon(Icons.refresh),
            onPressed: _manager.reset,
          ),
        ],
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            // 计数显示
            ValueListenableBuilder<int>(
              valueListenable: _manager.count,
              builder: (context, count, child) {
                return AnimatedDefaultTextStyle(
                  duration: const Duration(milliseconds: 200),
                  style: TextStyle(
                    fontSize: 72,
                    fontWeight: FontWeight.bold,
                    color: _getColorForCount(count),
                  ),
                  child: Text(count.toString()),
                );
              },
            ),
            const SizedBox(height: 8),
            
            // 状态指示
            ValueListenableBuilder<int>(
              valueListenable: _manager.count,
              builder: (context, count, child) {
                String status;
                IconData icon;
                Color color;
                
                if (count == 0) {
                  status = '初始状态';
                  icon = Icons.info_outline;
                  color = Colors.grey;
                } else if (count < 50) {
                  status = '正常范围';
                  icon = Icons.check_circle_outline;
                  color = Colors.green;
                } else if (count < 100) {
                  status = '接近上限';
                  icon = Icons.warning_amber;
                  color = Colors.orange;
                } else {
                  status = '已达上限';
                  icon = Icons.error_outline;
                  color = Colors.red;
                }
                
                return Container(
                  padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
                  decoration: BoxDecoration(
                    color: color.withOpacity(0.1),
                    borderRadius: BorderRadius.circular(20),
                  ),
                  child: Row(
                    mainAxisSize: MainAxisSize.min,
                    children: [
                      Icon(icon, color: color, size: 18),
                      const SizedBox(width: 8),
                      Text(status, style: TextStyle(color: color)),
                    ],
                  ),
                );
              },
            ),
            const SizedBox(height: 32),
            
            // 进度条
            Padding(
              padding: const EdgeInsets.symmetric(horizontal: 32),
              child: ValueListenableBuilder<int>(
                valueListenable: _manager.count,
                builder: (context, count, child) {
                  return Column(
                    children: [
                      ClipRRect(
                        borderRadius: BorderRadius.circular(8),
                        child: LinearProgressIndicator(
                          value: count / 100,
                          minHeight: 12,
                          backgroundColor: Colors.grey.shade200,
                          valueColor: AlwaysStoppedAnimation(
                            _getColorForCount(count),
                          ),
                        ),
                      ),
                      const SizedBox(height: 8),
                      Row(
                        mainAxisAlignment: MainAxisAlignment.spaceBetween,
                        children: [
                          Text('0', style: TextStyle(color: Colors.grey.shade600)),
                          Text('$count/100', style: TextStyle(color: Colors.grey.shade600)),
                          Text('100', style: TextStyle(color: Colors.grey.shade600)),
                        ],
                      ),
                    ],
                  );
                },
              ),
            ),
            const SizedBox(height: 48),
            
            // 控制按钮
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                // 减少按钮
                ValueListenableBuilder<bool>(
                  valueListenable: _manager.canDecrement,
                  builder: (context, canDecrement, child) {
                    return FloatingActionButton(
                      heroTag: 'decrement',
                      onPressed: canDecrement ? _manager.decrement : null,
                      backgroundColor: canDecrement ? Colors.blue : Colors.grey.shade300,
                      child: const Icon(Icons.remove, color: Colors.white),
                    );
                  },
                ),
                const SizedBox(width: 32),
                
                // 增加按钮
                ValueListenableBuilder<bool>(
                  valueListenable: _manager.canIncrement,
                  builder: (context, canIncrement, child) {
                    return FloatingActionButton(
                      heroTag: 'increment',
                      onPressed: canIncrement ? _manager.increment : null,
                      backgroundColor: canIncrement ? Colors.blue : Colors.grey.shade300,
                      child: const Icon(Icons.add, color: Colors.white),
                    );
                  },
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
  
  Color _getColorForCount(int count) {
    if (count == 0) return Colors.grey;
    if (count < 50) return Colors.blue;
    if (count < 100) return Colors.orange;
    return Colors.red;
  }
}

五、进阶技巧

🌟 5.1 组合多个 ValueNotifier

/// 组合多个 ValueNotifier
class CombinedValueNotifier<T1, T2> extends ValueNotifier<(T1, T2)> {
  final ValueNotifier<T1> first;
  final ValueNotifier<T2> second;
  
  CombinedValueNotifier(this.first, this.second) : super((first.value, second.value)) {
    first.addListener(_update);
    second.addListener(_update);
  }
  
  void _update() {
    value = (first.value, second.value);
  }
  
  
  void dispose() {
    first.removeListener(_update);
    second.removeListener(_update);
    super.dispose();
  }
}

// 使用示例
final ageNotifier = ValueNotifier<int>(25);
final nameNotifier = ValueNotifier<String>('张三');
final combined = CombinedValueNotifier(ageNotifier, nameNotifier);

// 监听组合值
ValueListenableBuilder<(int, String)>(
  valueListenable: combined,
  builder: (context, value, child) {
    final (age, name) = value;
    return Text('$name, $age 岁');
  },
)

🔄 5.2 派生 ValueNotifier

/// 派生 ValueNotifier
class DerivedValueNotifier<T, R> extends ValueNotifier<R> {
  final ValueNotifier<T> source;
  final R Function(T) mapper;
  
  DerivedValueNotifier(this.source, this.mapper) : super(mapper(source.value)) {
    source.addListener(_update);
  }
  
  void _update() {
    value = mapper(source.value);
  }
  
  
  void dispose() {
    source.removeListener(_update);
    super.dispose();
  }
}

// 使用示例
final priceNotifier = ValueNotifier<double>(100.0);
final formattedPrice = DerivedValueNotifier<double, String>(
  priceNotifier,
  (price) => ${price.toStringAsFixed(2)}',
);

// 监听派生值
ValueListenableBuilder<String>(
  valueListenable: formattedPrice,
  builder: (context, value, child) {
    return Text(value); // 显示 "¥100.00"
  },
)

⚡ 5.3 防抖 ValueNotifier

/// 防抖 ValueNotifier
class DebouncedValueNotifier<T> extends ValueNotifier<T> {
  final Duration delay;
  Timer? _timer;
  T _pendingValue;
  
  DebouncedValueNotifier(super.value, this.delay) : _pendingValue = value;
  
  
  set value(T newValue) {
    _pendingValue = newValue;
    _timer?.cancel();
    _timer = Timer(delay, () {
      super.value = _pendingValue;
    });
  }
  
  
  void dispose() {
    _timer?.cancel();
    super.dispose();
  }
}

// 使用示例
final searchQuery = DebouncedValueNotifier<String>('', Duration(milliseconds: 300));

// 用户输入时
TextField(
  onChanged: (value) {
    searchQuery.value = value; // 会在 300ms 后才真正更新
  },
);

六、最佳实践与注意事项

✅ 6.1 性能优化建议

  1. 及时释放资源:在 dispose 中调用 ValueNotifier.dispose(),避免内存泄漏。

  2. 使用 ValueListenableBuilder:相比手动 addListener,ValueListenableBuilder 会自动管理监听器生命周期。

  3. 避免过度重建:ValueListenableBuilder 只会重建 builder 中的内容,尽量缩小重建范围。

  4. 合理使用 child 参数:对于不变的部分,使用 ValueListenableBuilder 的 child 参数。

  5. 避免频繁更新:对于高频更新场景,考虑使用防抖或节流。

⚠️ 6.2 常见问题与解决方案

问题 原因 解决方案
内存泄漏 未调用 dispose() 在 State.dispose 中释放
UI 不更新 没有使用 ValueListenableBuilder 使用 ValueListenableBuilder 监听
值相同也通知 直接修改对象属性 创建新对象或使用深拷贝
多次重建 监听器重复添加 使用 ValueListenableBuilder 自动管理
性能问题 高频更新 使用防抖或节流

📝 6.3 代码规范建议

  1. 封装状态管理器:将相关的 ValueNotifier 封装成一个管理器类。

  2. 统一释放资源:在管理器中提供统一的 dispose 方法。

  3. 添加注释:对于复杂的状态逻辑,添加注释说明。

  4. 类型安全:始终指定 ValueNotifier 的泛型类型。

  5. 命名规范:使用描述性的名称,如 counterNotifiercounter


七、总结

本文详细介绍了 Flutter 中 ValueNotifier 组件的使用方法,从基础概念到高级技巧,帮助你掌握轻量级状态管理的核心能力。

核心要点回顾:

📌 ValueNotifier 基础:理解单一值状态管理和自动通知机制

📌 ValueListenableBuilder 使用:高效监听值变化并局部重建 UI

📌 实际应用:表单验证、购物车、主题切换等典型场景

📌 进阶技巧:组合、派生、防抖等高级用法

📌 最佳实践:资源释放、性能优化、代码规范

通过本文的学习,你应该能够独立开发简单的状态管理功能,并能够将 ValueNotifier 应用到更多场景中。


八、参考资料

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐