You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于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(),
          ),
        );
      },
    );
  }
}

尝试过的方法(未成功)

  1. 将ColorSelector改为StatefulWidget,在initState传入oldColor,但导致选择新颜色时组件无法正常重建。
  2. 尝试给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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.23 20:49:55