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

如何在Flutter中结合Hive与Riverpod实现自定义主题系统

实现基于Riverpod+Hive的自定义调色板主题系统

1. 完善自定义调色板抽象类与实现

先定义抽象调色板类,再实现各主题的具体配色:

abstract class CustomTheme {
  Color get backgroundColor;
  Color get primaryColor;
  Color get textColor;
  // 按需扩展更多配色属性
}

class LightCustomTheme implements CustomTheme {
  @override
  Color get backgroundColor => Colors.white;
  @override
  Color get primaryColor => Colors.blue;
  @override
  Color get textColor => Colors.black87;
}

class DarkCustomTheme implements CustomTheme {
  @override
  Color get backgroundColor => Colors.grey[900]!;
  @override
  Color get primaryColor => Colors.lightBlueAccent;
  @override
  Color get textColor => Colors.white70;
}

class ChristmasCustomTheme implements CustomTheme {
  @override
  Color get backgroundColor => Colors.red[900]!;
  @override
  Color get primaryColor => Colors.green[600]!;
  @override
  Color get textColor => Colors.white;
}

2. 用Riverpod创建主题状态管理

通过StateNotifierProvider统一管理主题状态,同时监听系统亮度变化与Hive存储:

// 定义主题类型枚举,对应Hive存储值
enum AppThemeType {
  system, // Hive无值时的状态
  light,  // 对应存储值1
  dark,   // 对应存储值2
  christmas, // 对应存储值3
}

final themeProvider = StateNotifierProvider<ThemeNotifier, CustomTheme>((ref) {
  return ThemeNotifier(ref);
});

class ThemeNotifier extends StateNotifier<CustomTheme> {
  final Ref ref;
  late final Box _themeBox;
  late final VoidCallback _systemBrightnessListener;

  ThemeNotifier(this.ref) : super(LightCustomTheme()) {
    _themeBox = Hive.box('themeBox');
    // 初始化加载保存的主题
    _loadSavedTheme();
    // 监听系统亮度变化(仅跟随系统模式时生效)
    _systemBrightnessListener = WidgetsBinding.instance.addPlatformBrightnessListener((brightness) {
      if (_getCurrentThemeType() == AppThemeType.system) {
        _updateSystemTheme(brightness);
      }
    });
  }

  @override
  void dispose() {
    WidgetsBinding.instance.removePlatformBrightnessListener(_systemBrightnessListener);
    super.dispose();
  }

  // 加载Hive中保存的主题配置
  void _loadSavedTheme() {
    final savedValue = _themeBox.get('theme');
    if (savedValue == null) {
      // 跟随系统,初始化时读取当前系统亮度
      final currentBrightness = WidgetsBinding.instance.window.platformBrightness;
      _updateSystemTheme(currentBrightness);
    } else {
      switch (savedValue) {
        case 1:
          state = LightCustomTheme();
          break;
        case 2:
          state = DarkCustomTheme();
          break;
        case 3:
          state = ChristmasCustomTheme();
          break;
      }
    }
  }

  // 根据系统亮度切换主题
  void _updateSystemTheme(Brightness brightness) {
    state = brightness == Brightness.light
        ? LightCustomTheme()
        : DarkCustomTheme();
  }

  // 获取当前主题类型
  AppThemeType _getCurrentThemeType() {
    final savedValue = _themeBox.get('theme');
    if (savedValue == null) return AppThemeType.system;
    switch (savedValue) {
      case 1: return AppThemeType.light;
      case 2: return AppThemeType.dark;
      case 3: return AppThemeType.christmas;
      default: return AppThemeType.system;
    }
  }

  // 对外暴露的主题切换方法
  void switchTheme(AppThemeType type) {
    switch (type) {
      case AppThemeType.system:
        _themeBox.delete('theme');
        final currentBrightness = WidgetsBinding.instance.window.platformBrightness;
        _updateSystemTheme(currentBrightness);
        break;
      case AppThemeType.light:
        _themeBox.put('theme', 1);
        state = LightCustomTheme();
        break;
      case AppThemeType.dark:
        _themeBox.put('theme', 2);
        state = DarkCustomTheme();
        break;
      case AppThemeType.christmas:
        _themeBox.put('theme', 3);
        state = ChristmasCustomTheme();
        break;
    }
  }
}

3. 应用入口集成ProviderScope

确保应用外层包裹ProviderScope,且不使用MaterialApp默认的theme参数:

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  // 已完成Hive初始化与themeBox打开
  await Hive.openBox('themeBox');

  runApp(
    const ProviderScope(
      child: MyApp(),
    ),
  );
}

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

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    return MaterialApp(
      home: const HomePage(),
      // 不配置默认theme参数
    );
  }
}

4. UI中直接调用自定义调色板

在任意ConsumerWidget中通过ref.watch获取当前主题,直接调用配色属性:

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

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final currentTheme = ref.watch(themeProvider);
    return Scaffold(
      backgroundColor: currentTheme.backgroundColor,
      appBar: AppBar(
        backgroundColor: currentTheme.primaryColor,
        title: Text(
          '自定义主题演示',
          style: TextStyle(color: currentTheme.textColor),
        ),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(
              '当前主题配色展示',
              style: TextStyle(color: currentTheme.textColor, fontSize: 18),
            ),
            const SizedBox(height: 20),
            // 主题切换按钮组
            Wrap(
              spacing: 10,
              children: [
                ElevatedButton(
                  onPressed: () => ref.read(themeProvider.notifier).switchTheme(AppThemeType.system),
                  child: const Text('跟随系统'),
                ),
                ElevatedButton(
                  onPressed: () => ref.read(themeProvider.notifier).switchTheme(AppThemeType.light),
                  child: const Text('浅色主题'),
                ),
                ElevatedButton(
                  onPressed: () => ref.read(themeProvider.notifier).switchTheme(AppThemeType.dark),
                  child: const Text('深色主题'),
                ),
                ElevatedButton(
                  onPressed: () => ref.read(themeProvider.notifier).switchTheme(AppThemeType.christmas),
                  child: const Text('圣诞主题'),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}

核心逻辑说明

  • 完全脱离Material默认主题:所有配色直接从Riverpod提供的CustomTheme实例获取,不依赖MaterialApp.theme参数
  • 系统亮度自动同步:跟随系统模式下,系统亮度变化会自动切换对应明暗主题
  • Hive存储适配:切换到系统模式时删除theme键,其他模式存储对应数值,完全匹配需求
  • 全局便捷访问:通过ref.watch(themeProvider)可在任意组件中直接调用配色属性

内容的提问来源于stack exchange,提问作者Florian Leeser

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.31 17:55:42