【Flutter for OpenHarmony】Flutter 深色模式与主题切换的鸿蒙化适配与实战指南
Flutter深色模式与主题切换实战指南 本文分享了Flutter深色模式适配的完整解决方案,特别针对OpenHarmony平台进行了优化。作者作为计算机专业学生,详细记录了从踩坑到实现的全过程。 主要内容包括: 主题系统架构:解析ThemeData和Theme组件的核心作用 三种主题模式实现:system(跟随系统)、light(固定浅色)、dark(固定深色) 完整颜色系统设计:包含主色系、浅
·
【Flutter for OpenHarmony】Flutter 深色模式与主题切换的鸿蒙化适配与实战指南
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
一、为什么我要做深色模式?
我是 IntMainJhy,上海某高校大一计算机专业的学生。说起深色模式,我真的有一段"真香"经历。
一开始我觉得深色模式就是"把背景改成黑色"嘛,有什么难的?结果做出来之后,我发现问题一大堆:
- 深色模式下文字看不清楚
- 按钮颜色和背景颜色撞色
- 切换主题时页面闪烁
- 有些组件完全没有响应主题变化
后来我花了整整两天时间研究 Flutter 的主题系统,才终于把深色模式做好。今天这篇文章,我就把我是怎么做的,全部分享出来。
二、深色模式基础
2.1 Flutter 主题系统
Flutter 的主题系统由两部分组成:
| 组件 | 说明 |
|---|---|
ThemeData |
定义主题数据(颜色、字体、样式等) |
Theme |
Widget,继承主题数据给子组件 |
2.2 主题模式
Flutter 支持三种主题模式:
| 模式 | 说明 |
|---|---|
ThemeMode.system |
跟随系统设置 |
ThemeMode.light |
始终浅色模式 |
ThemeMode.dark |
始终深色模式 |
三、完整主题配置
3.1 颜色系统
// lib/mental_health/theme/app_colors.dart
import 'package:flutter/material.dart';
/// 心理健康 App 颜色系统
class AppColors {
// ==================== 主色 ====================
/// 主色调 - 蓝紫色
static const Color primary = Color(0xFF6C63FF);
/// 主色深色
static const Color primaryDark = Color(0xFF5849BE);
/// 主色浅色
static const Color primaryLight = Color(0xFF9B93FF);
// ==================== 浅色主题颜色 ====================
/// 浅色背景色
static const Color lightBackground = Color(0xFFF8F9FE);
/// 浅色卡片色
static const Color lightCard = Colors.white;
/// 浅色分割线
static const Color lightDivider = Color(0xFFE0E0E0);
/// 浅色主要文字
static const Color lightTextPrimary = Color(0xFF2D3436);
/// 浅色次要文字
static const Color lightTextSecondary = Color(0xFF636E72);
// ==================== 深色主题颜色 ====================
/// 深色背景色
static const Color darkBackground = Color(0xFF121212);
/// 深色卡片色
static const Color darkCard = Color(0xFF1E1E1E);
/// 深色分割线
static const Color darkDivider = Color(0xFF2D2D2D);
/// 深色主要文字
static const Color darkTextPrimary = Color(0xFFFAFAFA);
/// 深色次要文字
static const Color darkTextSecondary = Color(0xFFB0B0B0);
// ==================== 心情颜色 ====================
/// 开心 - 绿色
static const Color moodHappy = Color(0xFF27AE60);
/// 平静 - 蓝色
static const Color moodCalm = Color(0xFF3498DB);
/// 一般 - 橙色
static const Color moodNeutral = Color(0xFFF39C12);
/// 难过 - 红色
static const Color moodSad = Color(0xFFE74C3C);
/// 疲惫 - 紫色
static const Color moodTired = Color(0xFF9B59B6);
// ==================== 功能色 ====================
/// 成功色
static const Color success = Color(0xFF27AE60);
/// 警告色
static const Color warning = Color(0xFFF39C12);
/// 错误色
static const Color error = Color(0xFFE74C3C);
/// 信息色
static const Color info = Color(0xFF3498DB);
}
3.2 主题数据配置
// lib/mental_health/theme/app_theme.dart
import 'package:flutter/material.dart';
import 'app_colors.dart';
/// App 主题配置
class AppTheme {
/// 浅色主题
static ThemeData get lightTheme => ThemeData(
useMaterial3: true,
brightness: Brightness.light,
// 主色调
primaryColor: AppColors.primary,
colorScheme: ColorScheme.light(
primary: AppColors.primary,
primaryContainer: AppColors.primaryLight,
secondary: AppColors.primary,
surface: AppColors.lightCard,
error: AppColors.error,
),
// Scaffold 背景
scaffoldBackgroundColor: AppColors.lightBackground,
// AppBar 主题
appBarTheme: const AppBarTheme(
backgroundColor: AppColors.lightCard,
foregroundColor: AppColors.lightTextPrimary,
elevation: 0,
centerTitle: true,
titleTextStyle: TextStyle(
color: AppColors.lightTextPrimary,
fontSize: 18,
fontWeight: FontWeight.w600,
),
),
// 卡片主题
cardTheme: CardTheme(
color: AppColors.lightCard,
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
),
// 按钮主题
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
backgroundColor: AppColors.primary,
foregroundColor: Colors.white,
elevation: 0,
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 14),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
),
// 文字按钮主题
textButtonTheme: TextButtonThemeData(
style: TextButton.styleFrom(
foregroundColor: AppColors.primary,
),
),
// 输入框主题
inputDecorationTheme: InputDecorationTheme(
filled: true,
fillColor: AppColors.lightCard,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide.none,
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: const BorderSide(color: AppColors.lightDivider),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: const BorderSide(color: AppColors.primary, width: 2),
),
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
),
// 底部导航栏主题
bottomNavigationBarTheme: const BottomNavigationBarThemeData(
backgroundColor: AppColors.lightCard,
selectedItemColor: AppColors.primary,
unselectedItemColor: AppColors.lightTextSecondary,
type: BottomNavigationBarType.fixed,
elevation: 8,
),
// 分割线主题
dividerTheme: const DividerThemeData(
color: AppColors.lightDivider,
thickness: 1,
),
// 图标主题
iconTheme: const IconThemeData(
color: AppColors.lightTextSecondary,
),
);
/// 深色主题
static ThemeData get darkTheme => ThemeData(
useMaterial3: true,
brightness: Brightness.dark,
// 主色调
primaryColor: AppColors.primary,
colorScheme: ColorScheme.dark(
primary: AppColors.primary,
primaryContainer: AppColors.primaryDark,
secondary: AppColors.primary,
surface: AppColors.darkCard,
error: AppColors.error,
),
// Scaffold 背景
scaffoldBackgroundColor: AppColors.darkBackground,
// AppBar 主题
appBarTheme: const AppBarTheme(
backgroundColor: AppColors.darkCard,
foregroundColor: AppColors.darkTextPrimary,
elevation: 0,
centerTitle: true,
titleTextStyle: TextStyle(
color: AppColors.darkTextPrimary,
fontSize: 18,
fontWeight: FontWeight.w600,
),
),
// 卡片主题
cardTheme: CardTheme(
color: AppColors.darkCard,
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
),
// 按钮主题
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
backgroundColor: AppColors.primary,
foregroundColor: Colors.white,
elevation: 0,
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 14),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
),
// 文字按钮主题
textButtonTheme: TextButtonThemeData(
style: TextButton.styleFrom(
foregroundColor: AppColors.primary,
),
),
// 输入框主题
inputDecorationTheme: InputDecorationTheme(
filled: true,
fillColor: AppColors.darkCard,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide.none,
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: const BorderSide(color: AppColors.darkDivider),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: const BorderSide(color: AppColors.primary, width: 2),
),
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
),
// 底部导航栏主题
bottomNavigationBarTheme: const BottomNavigationBarThemeData(
backgroundColor: AppColors.darkCard,
selectedItemColor: AppColors.primary,
unselectedItemColor: AppColors.darkTextSecondary,
type: BottomNavigationBarType.fixed,
elevation: 8,
),
// 分割线主题
dividerTheme: const DividerThemeData(
color: AppColors.darkDivider,
thickness: 1,
),
// 图标主题
iconTheme: const IconThemeData(
color: AppColors.darkTextSecondary,
),
);
}
四、主题 Provider
// lib/mental_health/providers/theme_provider.dart
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
/// 主题模式
enum AppThemeMode {
system('跟随系统', Icons.brightness_auto),
light('浅色模式', Icons.light_mode),
dark('深色模式', Icons.dark_mode);
final String label;
final IconData icon;
const AppThemeMode(this.label, this.icon);
}
/// 主题 Provider
class ThemeProvider extends ChangeNotifier {
static const String _themeModeKey = 'theme_mode';
ThemeMode _themeMode = ThemeMode.system;
bool _isLoading = true;
ThemeMode get themeMode => _themeMode;
bool get isLoading => _isLoading;
/// 获取当前的 AppThemeMode
AppThemeMode get appThemeMode {
switch (_themeMode) {
case ThemeMode.system:
return AppThemeMode.system;
case ThemeMode.light:
return AppThemeMode.light;
case ThemeMode.dark:
return AppThemeMode.dark;
}
}
/// 初始化
Future<void> initialize() async {
_isLoading = true;
notifyListeners();
try {
final prefs = await SharedPreferences.getInstance();
final savedMode = prefs.getString(_themeModeKey);
if (savedMode != null) {
_themeMode = _parseThemeMode(savedMode);
}
} catch (e) {
debugPrint('加载主题设置失败: $e');
} finally {
_isLoading = false;
notifyListeners();
}
}
/// 设置主题模式
Future<void> setThemeMode(AppThemeMode mode) async {
_themeMode = _parseAppThemeMode(mode);
// 保存设置
final prefs = await SharedPreferences.getInstance();
await prefs.setString(_themeModeKey, mode.name);
notifyListeners();
}
/// 切换主题
Future<void> toggleTheme() async {
final currentMode = appThemeMode;
final nextMode = (currentMode.index + 1) % AppThemeMode.values.length;
await setThemeMode(AppThemeMode.values[nextMode]);
}
ThemeMode _parseThemeMode(String mode) {
switch (mode) {
case 'system':
return ThemeMode.system;
case 'light':
return ThemeMode.light;
case 'dark':
return ThemeMode.dark;
default:
return ThemeMode.system;
}
}
ThemeMode _parseAppThemeMode(AppThemeMode mode) {
switch (mode) {
case AppThemeMode.system:
return ThemeMode.system;
case AppThemeMode.light:
return ThemeMode.light;
case AppThemeMode.dark:
return ThemeMode.dark;
}
}
}
五、主题设置页面
// lib/mental_health/screens/theme_settings_screen.dart
import 'package:flutter/material.dart';
import '../providers/theme_provider.dart';
class ThemeSettingsScreen extends StatelessWidget {
const ThemeSettingsScreen({super.key});
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('主题设置'),
),
body: Consumer<ThemeProvider>(
builder: (context, provider, child) {
if (provider.isLoading) {
return const Center(child: CircularProgressIndicator());
}
return ListView(
padding: const EdgeInsets.all(16),
children: [
// 当前主题预览
_buildThemePreview(context),
const SizedBox(height: 24),
// 主题选项
const Text(
'选择主题模式',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 12),
// 主题选项列表
...AppThemeMode.values.map((mode) {
final isSelected = provider.appThemeMode == mode;
return _buildThemeOption(
context,
provider,
mode,
isSelected,
);
}),
],
);
},
),
);
}
Widget _buildThemePreview(BuildContext context) {
final theme = Theme.of(context);
return Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'主题预览',
style: TextStyle(fontWeight: FontWeight.bold),
),
const SizedBox(height: 16),
Row(
children: [
// 浅色预览
Expanded(
child: Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: const Color(0xFFF8F9FE),
borderRadius: BorderRadius.circular(12),
border: Border.all(color: Colors.grey[300]!),
),
child: Column(
children: [
Container(
width: 40,
height: 40,
decoration: BoxDecoration(
color: theme.primaryColor,
borderRadius: BorderRadius.circular(8),
),
),
const SizedBox(height: 8),
Text(
'浅色',
style: TextStyle(
fontSize: 12,
color: Colors.grey[700],
),
),
],
),
),
),
const SizedBox(width: 12),
// 深色预览
Expanded(
child: Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: const Color(0xFF121212),
borderRadius: BorderRadius.circular(12),
border: Border.all(color: Colors.grey[700]!),
),
child: Column(
children: [
Container(
width: 40,
height: 40,
decoration: BoxDecoration(
color: theme.primaryColor,
borderRadius: BorderRadius.circular(8),
),
),
const SizedBox(height: 8),
Text(
'深色',
style: TextStyle(
fontSize: 12,
color: Colors.grey[300],
),
),
],
),
),
),
],
),
],
),
),
);
}
Widget _buildThemeOption(
BuildContext context,
ThemeProvider provider,
AppThemeMode mode,
bool isSelected,
) {
return Card(
margin: const EdgeInsets.only(bottom: 12),
child: RadioListTile<AppThemeMode>(
value: mode,
groupValue: provider.appThemeMode,
onChanged: (value) {
if (value != null) {
provider.setThemeMode(value);
}
},
title: Row(
children: [
Icon(mode.icon),
const SizedBox(width: 12),
Text(mode.label),
],
),
subtitle: Text(_getDescription(mode)),
secondary: isSelected
? Container(
padding: const EdgeInsets.all(4),
decoration: BoxDecoration(
color: Theme.of(context).primaryColor,
shape: BoxShape.circle,
),
child: const Icon(
Icons.check,
color: Colors.white,
size: 16,
),
)
: null,
),
);
}
String _getDescription(AppThemeMode mode) {
switch (mode) {
case AppThemeMode.system:
return '根据系统设置自动切换';
case AppThemeMode.light:
return '始终使用浅色模式';
case AppThemeMode.dark:
return '始终使用深色模式';
}
}
}
六、在应用中使用主题
// main.dart
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// 初始化主题 Provider
final themeProvider = ThemeProvider();
await themeProvider.initialize();
runApp(
ChangeNotifierProvider.value(
value: themeProvider,
child: const MentalHealthApp(),
),
);
}
class MentalHealthApp extends StatelessWidget {
const MentalHealthApp({super.key});
Widget build(BuildContext context) {
return Consumer<ThemeProvider>(
builder: (context, themeProvider, child) {
return MaterialApp(
title: '心理健康',
debugShowCheckedModeBanner: false,
// 主题模式
themeMode: themeProvider.themeMode,
// 浅色主题
theme: AppTheme.lightTheme,
// 深色主题
darkTheme: AppTheme.darkTheme,
home: const MentalHealthHomeScreen(),
);
},
);
}
}
七、自定义组件的主题适配
7.1 使用 Theme.of(context)
// 自定义组件使用主题颜色
class MoodCard extends StatelessWidget {
final Color moodColor;
final String label;
const MoodCard({
super.key,
required this.moodColor,
required this.label,
});
Widget build(BuildContext context) {
// 使用 context 获取主题
final theme = Theme.of(context);
return Card(
color: theme.cardColor, // 自动适配主题
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
children: [
Container(
width: 60,
height: 60,
decoration: BoxDecoration(
color: moodColor.withOpacity(0.2),
shape: BoxShape.circle,
),
child: Icon(
Icons.mood,
color: moodColor,
),
),
const SizedBox(height: 8),
Text(
label,
style: TextStyle(
color: theme.textTheme.bodyLarge?.color, // 自动适配文字颜色
),
),
],
),
),
);
}
}
7.2 使用 ColorScheme
// 使用 ColorScheme
class ThemedButton extends StatelessWidget {
const ThemedButton({super.key});
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return ElevatedButton(
onPressed: () {},
style: ElevatedButton.styleFrom(
backgroundColor: colorScheme.primary, // 使用主色
foregroundColor: colorScheme.onPrimary, // 使用主色上的文字色
),
child: const Text('按钮'),
);
}
}
八、鸿蒙平台专属适配
适配点:鸿蒙系统主题检测
问题:鸿蒙设备的主题检测可能和标准 Android 不同。
解决方案:
// 在 ThemeProvider 中添加鸿蒙特定逻辑
Future<void> _detectSystemTheme() async {
// 鸿蒙设备使用 MediaQuery 检测
final brightness = MediaQuery.platformBrightnessOf(context);
switch (brightness) {
case Brightness.light:
_themeMode = ThemeMode.light;
break;
case Brightness.dark:
_themeMode = ThemeMode.dark;
break;
}
}
九、我的踩坑记录
坑1:主题切换闪烁
问题:切换主题时页面闪烁。
原因:没有使用 themeMode 参数。
错误代码:
// ❌ 错误
MaterialApp(
theme: isDark ? darkTheme : lightTheme,
home: HomePage(),
)
解决代码:
// ✅ 正确
MaterialApp(
theme: lightTheme,
darkTheme: darkTheme,
themeMode: currentThemeMode, // 使用 themeMode
home: HomePage(),
)
坑2:自定义颜色不响应主题
问题:手动设置的颜色不随主题切换。
原因:使用了硬编码的颜色。
解决代码:
// ✅ 使用主题颜色
Container(
color: Theme.of(context).colorScheme.surface,
)
坑3:深色模式文字不清晰
问题:深色模式下文字看不清楚。
原因:使用了浅色主题的文字颜色。
解决代码:
// ✅ 使用主题的文字颜色
Text(
'文字',
style: TextStyle(
color: Theme.of(context).textTheme.bodyLarge?.color,
),
)
十、功能验证清单
| 序号 | 检查项 | 测试场景 | 预期结果 |
|---|---|---|---|
| 1 | 浅色主题 | 选择浅色模式 | 背景变白 |
| 2 | 深色主题 | 选择深色模式 | 背景变黑 |
| 3 | 系统主题 | 系统切换主题 | App 跟随变化 |
| 4 | 主题保存 | 退出 App | 主题设置保留 |
| 5 | 组件适配 | 各页面 | 所有组件正确响应主题 |
十一、大一学生真实学习总结
深色模式让我彻底理解了 Flutter 的主题系统。
最重要的几点:
-
不要硬编码颜色
- 所有颜色都要使用主题颜色
- 这样才能响应主题切换
-
themeMode 是关键
- 使用
themeMode而不是直接切换theme - 这样动画更平滑,不会闪烁
- 使用
-
ColorScheme 是好帮手
- 合理使用
ColorScheme - 减少自定义颜色
- 合理使用
-
测试要全面
- 浅色模式测试一遍
- 深色模式测试一遍
- 系统模式测试一遍
作者:IntMainJhy
创作时间:2026年5月


更多推荐




所有评论(0)