基于Riverpod实现编辑页面颜色选择器预设旧习惯颜色
问题
我正在开发一个编辑已有习惯的页面,习惯带有颜色属性,需要把当前待编辑习惯的旧颜色传递给color_selector模态框,实现点击组件时预设选中旧颜色(比如习惯颜色是紫色,打开模态框时紫色就处于选中状态)。
当前代码(创建新习惯时的颜色选择控制器)
/// Notifier class for exposing a habit's color state. @riverpod class SelectColorNotifier extends _$SelectColorNotifier { @override // use the last selected color when re-opening the color modal. int build() => ref.watch(newColorProvider); void updateColor(int selectedColor) { if (selectedColor == state) return; state = selectedColor; } } /// Notifier class for keeping track of the selected color /// from `selectColorNotifierProvider`. /// /// Used to set the selected color from the color modal only when the save icon /// is pressed. @riverpod class NewColor extends _$NewColor { @override int build() => habitColors.first; void updateNewColor(int newColor) { if (newColor == state) return; state = newColor; } }
color_selector组件代码
import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:habitin/src/common_widgets/rounded_padding.dart'; import 'package:habitin/src/constants/constants.dart'; import 'package:habitin/src/features/add_edit/view/new_habit_controller.dart'; class ColorSelector extends StatelessWidget { const ColorSelector({super.key}); @override Widget build(BuildContext context) { return GestureDetector( onTap: () => showColorModalSheet(context), child: RoundedPadding( vertical: 8, horizontal: 8, child: Row( mainAxisSize: MainAxisSize.min, children: [ Consumer( builder: (_, ref, __) { final selectedColor = ref.watch(newColorProvider); return RoundedPadding( vertical: 6, horizontal: 6, radius: 14, color: Theme.of(context).colorScheme.onSecondary, child: Container( height: 38, width: 40, decoration: BoxDecoration( shape: BoxShape.circle, color: Color(selectedColor), ), ), ); }, ), gapW16, Text("COLOR", style: Theme.of(context).textTheme.bodySmall) ], ), ), ); } Future<void> showColorModalSheet(BuildContext context) { return showModalBottomSheet( enableDrag: false, isScrollControlled: true, isDismissible: false, context: context, builder: (_) => Container( padding: const EdgeInsets.all(20), child: Column( mainAxisSize: MainAxisSize.min, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ IconButton( icon: const Icon(Icons.close_rounded), onPressed: () => Navigator.pop(context), ), Text( "Select Color", style: Theme.of(context).textTheme.bodyLarge, ), Consumer( builder: (_, ref, __) { return IconButton( icon: const Icon(Icons.check_rounded), onPressed: () { final selectedColor = ref.read(selectColorNotifierProvider); ref .read(newColorProvider.notifier) .updateNewColor(selectedColor); Navigator.pop(context); }, ); }, ), ], ), gapH30, Wrap( spacing: 28.0, runSpacing: 20.0, children: habitColors.map((color) => ColorChip(color: color)).toList(), ), gapH20, ], ), ), ); } } /// Builds the individual Color Chip widgets used in [ColorSelector]. class ColorChip extends StatelessWidget { const ColorChip({super.key, required this.color, this.oldColor}); final int color; final int? oldColor; @override Widget build(BuildContext context) { return Consumer( builder: (_, ref, __) { final selectedColor = ref.watch(selectColorNotifierProvider); return GestureDetector( onTap: () => ref.read(selectColorNotifierProvider.notifier).updateColor(color), child: Container( height: 60, width: 60, decoration: BoxDecoration( shape: BoxShape.circle, color: Color(color), border: selectedColor == color ? Border.all( width: 3.0, color: Theme.of(context).colorScheme.onBackground, ) : const Border(), ), child: selectedColor == color ? const Icon(Icons.check_rounded) : const SizedBox.shrink(), ), ); }, ); } }
尝试过的方法(未成功)
- 将
ColorSelector改为StatefulWidget,在initState传入oldColor,但导致选择新颜色时组件无法正常重建。 - 尝试给
NewColorProvider添加oldColor参数作为初始状态,但未成功实现:
/// Notifier class for exposing a habit's color state. @riverpod class SelectColorNotifier extends _$SelectColorNotifier { @override // use the last selected color when re-opening the color modal. int build() => ref.watch(newColorProvider); void updateColor(int selectedColor) { if (selectedColor == state) return; state = selectedColor; } } /// Notifier class for keeping track of the selected color /// from `selectColorNotifierProvider`. /// /// Used to set the selected color from the color modal only when the save icon /// is pressed. @riverpod class NewColor extends _$NewColor { @override // set the initial state to be the current habit color. int build(int? oldColor) => oldColor ?? habitColors.first; void updateNewColor(int newColor) { if (newColor == state) return; state = newColor; } }
需要解决的问题:如何实现编辑页面颜色选择器预设旧习惯颜色的功能?
解决方案
1. 修改Provider支持初始化旧颜色
让NewColor和SelectColorNotifier都支持接收initialColor参数,实现新习惯/编辑模式的状态区分:
/// 管理颜色选择器的临时选中状态 @riverpod class SelectColorNotifier extends _$SelectColorNotifier { @override int build(int initialColor) { // 编辑模式传入旧颜色,新习惯传入默认颜色 return initialColor; } void updateColor(int selectedColor) { if (selectedColor == state) return; state = selectedColor; } } /// 管理最终确认的颜色(点击保存后更新) @riverpod class NewColor extends _$NewColor { @override int build(int initialColor) => initialColor; void updateNewColor(int newColor) { if (newColor == state) return; state = newColor; } }
2. 修改ColorSelector组件,接收初始颜色参数
调整组件为接收initialColor,并在模态框内局部管理临时状态:
class ColorSelector extends StatelessWidget { // 新增:接收初始颜色(编辑模式传旧颜色,新习惯传默认颜色) const ColorSelector({super.key, required this.initialColor}); final int initialColor; @override Widget build(BuildContext context) { return GestureDetector( onTap: () => showColorModalSheet(context), child: RoundedPadding( vertical: 8, horizontal: 8, child: Row( mainAxisSize: MainAxisSize.min, children: [ Consumer( builder: (_, ref, __) { // 监听最终确认的颜色 final selectedColor = ref.watch(newColorProvider(initialColor)); return RoundedPadding( vertical: 6, horizontal: 6, radius: 14, color: Theme.of(context).colorScheme.onSecondary, child: Container( height: 38, width: 40, decoration: BoxDecoration( shape: BoxShape.circle, color: Color(selectedColor), ), ), ); }, ), gapW16, Text("COLOR", style: Theme.of(context).textTheme.bodySmall) ], ), ), ); } Future<void> showColorModalSheet(BuildContext context) { return showModalBottomSheet( enableDrag: false, isScrollControlled: true, isDismissible: false, context: context, builder: (_) => ProviderScope( // 局部覆盖临时选择器的初始状态,避免全局状态污染 overrides: [ selectColorNotifierProvider.overrideWith((ref) => SelectColorNotifier()..state = ref.watch(newColorProvider(initialColor))), ], child: Container( padding: const EdgeInsets.all(20), child: Column( mainAxisSize: MainAxisSize.min, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ IconButton( icon: const Icon(Icons.close_rounded), onPressed: () => Navigator.pop(context), ), Text( "Select Color", style: Theme.of(context).textTheme.bodyLarge, ), Consumer( builder: (_, ref, __) { return IconButton( icon: const Icon(Icons.check_rounded), onPressed: () { final selectedColor = ref.read(selectColorNotifierProvider(initialColor)); ref.read(newColorProvider(initialColor).notifier).updateNewColor(selectedColor); Navigator.pop(context); }, ); }, ), ], ), gapH30, Wrap( spacing: 28.0, runSpacing: 20.0, children: habitColors.map((color) => ColorChip(color: color, initialColor: initialColor)).toList(), ), gapH20, ], ), ), ), ); } }
3. 调整ColorChip的Provider监听
确保ColorChip监听带参数的selectColorNotifierProvider:
class ColorChip extends StatelessWidget { const ColorChip({super.key, required this.color, required this.initialColor}); final int color; final int initialColor; @override Widget build(BuildContext context) { return Consumer( builder: (_, ref, __) { final selectedColor = ref.watch(selectColorNotifierProvider(initialColor)); return GestureDetector( onTap: () => ref.read(selectColorNotifierProvider(initialColor).notifier).updateColor(color), child: Container( height: 60, width: 60, decoration: BoxDecoration( shape: BoxShape.circle, color: Color(color), border: selectedColor == color ? Border.all( width: 3.0, color: Theme.of(context).colorScheme.onBackground, ) : const Border(), ), child: selectedColor == color ? const Icon(Icons.check_rounded) : const SizedBox.shrink(), ), ); }, ); } }
4. 使用组件时传入初始颜色
- 新习惯页面:传入默认颜色
ColorSelector(initialColor: habitColors.first) - 编辑页面:传入待编辑习惯的旧颜色
ColorSelector(initialColor: existingHabit.color)
关键说明
- 通过给Provider添加参数,让同一个Provider适配新习惯/编辑两种场景,避免全局状态冲突。
- 模态框内使用
ProviderScope局部覆盖状态,确保每次打开模态框时,临时选择器的初始值都是当前已确认的颜色(编辑模式即为旧颜色)。 - 保持组件为StatelessWidget,利用Riverpod的状态监听自动重建,避免StatefulWidget带来的状态不同步问题。
内容的提问来源于stack exchange,提问作者nonsocchi
相关产品推荐
相关产品推荐

