Flutter for OpenHarmony轻量级开源记事本app实战:主题设置
本文介绍了Flutter应用中实现主题切换功能的完整方案。通过GetX状态管理实现响应式主题切换,提供浅色/深色两种模式选项,并支持实时预览效果。关键实现包括:1)使用RadioListTile构建主题选择卡片;2)创建动态预览卡片展示主题效果;3)通过RxBool管理主题状态并持久化存储;4)在MaterialApp中全局应用主题设置。该方案具有代码简洁、响应快速的特点,为用户提供了直观友好的主
设计理念
深色模式已经成为现代应用的标配功能,它不仅能够保护眼睛,还能节省电量。一个好的主题设置应该提供清晰的选项和实时预览效果。本文将详细介绍如何实现主题切换功能。
页面的基础结构
主题设置页面提供浅色和深色两种模式的选择,并展示预览效果。
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import '../../controllers/note_controller.dart';
class ThemePage extends StatelessWidget {
const ThemePage({super.key});
Widget build(BuildContext context) {
final controller = Get.find<NoteController>();
return Scaffold(
appBar: AppBar(
title: const Text('主题设置'),
),
body: Obx(() => ListView(
padding: EdgeInsets.all(16.w),
children: [
_buildThemeCard(controller),
SizedBox(height: 16.h),
_buildPreviewCard(controller),
],
)),
);
}
}
页面使用StatelessWidget,状态管理交给GetX控制器。body使用Obx包裹ListView,实现响应式更新。页面内容分为两个卡片:主题选择和预览效果。每个卡片之间有16像素的间距。
主题选择卡片
主题选择卡片使用RadioListTile提供单选功能。
Widget _buildThemeCard(NoteController controller) {
return Card(
child: Column(
children: [
RadioListTile<bool>(
title: const Text('浅色模式'),
subtitle: const Text('明亮的界面风格'),
secondary: const Icon(Icons.light_mode),
value: false,
groupValue: controller.isDarkMode.value,
onChanged: (value) {
controller.isDarkMode.value = value!;
controller.saveData();
},
),
const Divider(height: 1),
RadioListTile<bool>(
title: const Text('深色模式'),
subtitle: const Text('护眼的暗色风格'),
secondary: const Icon(Icons.dark_mode),
value: true,
groupValue: controller.isDarkMode.value,
onChanged: (value) {
controller.isDarkMode.value = value!;
controller.saveData();
},
),
],
),
);
}
第一个选项是浅色模式,value设置为false。RadioListTile的title显示模式名称,subtitle显示简短描述,secondary显示图标。groupValue绑定到controller.isDarkMode,onChanged回调在选择时更新值并保存。
预览效果卡片
预览卡片展示当前主题下的界面效果,让用户直观感受。
Widget _buildPreviewCard(NoteController controller) {
return Card(
child: Padding(
padding: EdgeInsets.all(16.w),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'预览',
style: TextStyle(
fontSize: 16.sp,
fontWeight: FontWeight.bold,
),
),
SizedBox(height: 12.h),
_buildPreviewSample(controller),
border: Border.all(color: Colors.grey.withOpacity(0.3)),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'示例笔记标题',
style: TextStyle(
fontSize: 16.sp,
fontWeight: FontWeight.bold,
color: controller.isDarkMode.value
? Colors.white
: Colors.black,
),
),
SizedBox(height: 4.h),
Text(
'这是笔记内容的预览效果...',
style: TextStyle(
fontSize: 14.sp,
color: Colors.grey,
),
),
],
),
),
],
),
),
);
}
预览卡片的标题使用粗体大字号,与主题选择卡片保持一致的样式。标题和预览内容之间有12像素的间距。crossAxisAlignment设置为start让内容左对齐。这种统一的设计风格让整个页面看起来更加协调。
预览容器的背景色根据当前主题动态变化。深色模式使用深灰色(0xFF1E1E1E),浅色模式使用白色。borderRadius设置圆角,border添加淡灰色边框。标题文字的颜色也根据主题变化,深色模式用白色,浅色模式用黑色。
主题的响应式管理
控制器中使用RxBool管理主题模式,实现响应式更新。
final RxBool isDarkMode = false.obs;
void loadData() async {
final prefs = await SharedPreferences.getInstance();
isDarkMode.value = prefs.getBool('isDarkMode') ?? false;
}
void saveData() async {
final prefs = await SharedPreferences.getInstance();
await prefs.setBool('isDarkMode', isDarkMode.value);
}
isDarkMode使用RxBool实现响应式,任何修改都会自动通知UI更新。loadData方法从SharedPreferences加载保存的主题模式,默认值为false(浅色模式)。saveData方法将当前主题模式保存到本地存储。这种持久化设计确保用户的设置在应用重启后仍然有效。
主题的全局应用
主题设置需要应用到整个应用,通过MaterialApp的theme和darkTheme实现。
class MyApp extends StatelessWidget {
Widget build(BuildContext context) {
final controller = Get.put(NoteController());
return Obx(() => MaterialApp(
title: '轻记',
theme: ThemeData(
brightness: Brightness.light,
primarySwatch: Colors.blue,
useMaterial3: true,
),
darkTheme: ThemeData(
brightness: Brightness.dark,
primarySwatch: Colors.blue,
useMaterial3: true,
),
themeMode: controller.isDarkMode.value
? ThemeMode.dark
: ThemeMode.light,
home: const MainPage(),
));
}
}
MaterialApp使用Obx包裹,实现主题的响应式切换。theme定义浅色主题,darkTheme定义深色主题。themeMode根据controller.isDarkMode的值动态切换。这种设计让主题切换能够立即应用到整个应用,无需重启。
主题的颜色适配
不同主题下,某些颜色需要适配以保持良好的可读性。
final isDark = Theme.of(context).brightness == Brightness.dark;
final bgColor = isDark ? const Color(0xFF1E1E1E) : Colors.white;
final textColor = isDark ? Colors.white : Colors.black;
Card(
color: bgColor,
child: Text(
'示例文字',
style: TextStyle(color: textColor),
),
)
通过Theme.of(context).brightness获取当前主题的亮度。根据亮度选择合适的背景色和文字颜色。深色模式使用深灰色背景和白色文字,浅色模式使用白色背景和黑色文字。这种适配确保在两种模式下都有良好的视觉效果。
主题的自动切换
可以根据系统设置自动切换主题,提供更智能的体验。
enum ThemeMode {
light,
dark,
system,
}
final Rx<ThemeMode> themeMode = ThemeMode.system.obs;
ThemeMode get effectiveThemeMode {
if (themeMode.value == ThemeMode.system) {
final brightness = WidgetsBinding.instance.window.platformBrightness;
return brightness == Brightness.dark ? ThemeMode.dark : ThemeMode.light;
}
return themeMode.value;
}
定义ThemeMode枚举,包含浅色、深色和跟随系统三种选项。effectiveThemeMode计算实际使用的主题模式,如果设置为跟随系统,就根据系统的platformBrightness决定。这种设计让用户可以选择手动控制或跟随系统。
主题切换的动画
主题切换时可以添加动画过渡,让变化更加平滑。
AnimatedTheme(
duration: const Duration(milliseconds: 300),
data: controller.isDarkMode.value ? darkTheme : lightTheme,
child: MaterialApp(
home: const MainPage(),
),
)
使用AnimatedTheme包裹MaterialApp,主题切换时会有300毫秒的过渡动画。这种平滑的过渡让用户体验更加舒适,避免突兀的颜色变化。duration可以根据需要调整,太短会显得仓促,太长会显得拖沓。
主题的预设方案
除了深浅两种基础模式,还可以提供多种预设主题方案。
enum ThemeScheme {
blue,
green,
purple,
orange,
}
ThemeData getThemeData(ThemeScheme scheme, bool isDark) {
final colorScheme = switch (scheme) {
ThemeScheme.blue => Colors.blue,
ThemeScheme.green => Colors.green,
ThemeScheme.purple => Colors.purple,
ThemeScheme.orange => Colors.orange,
};
return ThemeData(
brightness: isDark ? Brightness.dark : Brightness.light,
primarySwatch: colorScheme,
useMaterial3: true,
);
}
定义ThemeScheme枚举,包含多种颜色方案。getThemeData方法根据方案和模式生成对应的ThemeData。这种设计让用户可以选择自己喜欢的主题颜色,提供更个性化的体验。
主题的保存和加载
主题设置需要持久化保存,确保应用重启后仍然有效。
void saveTheme() async {
final prefs = await SharedPreferences.getInstance();
await prefs.setBool('isDarkMode', isDarkMode.value);
await prefs.setString('themeScheme', themeScheme.value.name);
}
void loadTheme() async {
final prefs = await SharedPreferences.getInstance();
isDarkMode.value = prefs.getBool('isDarkMode') ?? false;
final schemeName = prefs.getString('themeScheme') ?? 'blue';
themeScheme.value = ThemeScheme.values.firstWhere(
(e) => e.name == schemeName,
orElse: () => ThemeScheme.blue,
);
}
saveTheme方法保存主题模式和颜色方案到SharedPreferences。loadTheme方法加载保存的设置,如果没有保存的值就使用默认值。这种持久化设计确保用户的个性化设置不会丢失。
主题的实时预览
在设置页面提供实时预览,让用户在切换前就能看到效果。
Widget buildThemePreview(bool isDark) {
return Container(
padding: EdgeInsets.all(16.w),
decoration: BoxDecoration(
color: isDark ? const Color(0xFF1E1E1E) : Colors.white,
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: Colors.grey.withOpacity(0.3),
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
height: 40.h,
color: const Color(0xFF2196F3),
child: Center(
child: Text(
'AppBar',
style: TextStyle(color: Colors.white, fontSize: 16.sp),
),
),
),
SizedBox(height: 8.h),
Text(
'笔记标题',
style: TextStyle(
fontSize: 16.sp,
fontWeight: FontWeight.bold,
color: isDark ? Colors.white : Colors.black,
),
),
Text(
'笔记内容预览...',
style: TextStyle(
fontSize: 14.sp,
color: Colors.grey,
),
),
],
),
);
}
buildThemePreview方法创建一个主题预览组件,模拟真实的应用界面。包含AppBar、标题和内容等元素,让用户可以直观地看到主题效果。这种预览功能能够帮助用户做出更好的选择。
主题的导出和导入
支持导出和导入主题设置,方便在不同设备间同步。
Map<String, dynamic> exportTheme() {
return {
'isDarkMode': isDarkMode.value,
'themeScheme': themeScheme.value.name,
'version': '1.0',
};
}
void importTheme(Map<String, dynamic> data) {
isDarkMode.value = data['isDarkMode'] ?? false;
final schemeName = data['themeScheme'] ?? 'blue';
themeScheme.value = ThemeScheme.values.firstWhere(
(e) => e.name == schemeName,
orElse: () => ThemeScheme.blue,
);
saveTheme();
}
exportTheme方法将主题设置导出为Map,可以转换为JSON字符串。importTheme方法从Map导入主题设置。这种导出导入功能让用户可以在多个设备间保持一致的主题设置。
主题的重置功能
提供重置功能,让用户可以快速恢复到默认设置。
void resetTheme() {
isDarkMode.value = false;
themeScheme.value = ThemeScheme.blue;
saveTheme();
Get.snackbar('提示', '已重置为默认主题',
snackPosition: SnackPosition.BOTTOM);
}
resetTheme方法将主题重置为浅色模式和蓝色方案,然后保存并显示提示消息。这个功能可以在设置页面添加一个重置按钮,让用户在调节不满意时快速恢复。
主题的可访问性
主题设置应该考虑可访问性,确保所有用户都能使用。
Semantics(
label: '主题模式选择',
hint: '选择浅色或深色模式',
child: RadioListTile<bool>(
title: const Text('浅色模式'),
value: false,
groupValue: controller.isDarkMode.value,
onChanged: (value) {
controller.isDarkMode.value = value!;
controller.saveData();
},
),
)
为主题选择添加Semantics标签,帮助屏幕阅读器理解组件的用途。label描述组件是什么,hint提供使用提示。这些语义信息让视障用户也能顺利使用主题设置功能。
主题的性能优化
主题切换应该是高效的,避免不必要的重建。
class ThemeProvider extends InheritedWidget {
final bool isDarkMode;
final Function(bool) onThemeChanged;
const ThemeProvider({
Key? key,
required this.isDarkMode,
required this.onThemeChanged,
required Widget child,
}) : super(key: key, child: child);
static ThemeProvider? of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType<ThemeProvider>();
}
bool updateShouldNotify(ThemeProvider oldWidget) {
return isDarkMode != oldWidget.isDarkMode;
}
}
使用InheritedWidget提供主题数据,只有当主题真正改变时才通知子组件重建。updateShouldNotify方法比较新旧值,只有不同时才返回true。这种优化能够避免不必要的重建,提升性能。
主题的调试工具
在开发阶段,可以添加调试工具快速切换主题。
Widget buildThemeDebugger() {
return FloatingActionButton(
mini: true,
onPressed: () {
isDarkMode.value = !isDarkMode.value;
},
child: Icon(
isDarkMode.value ? Icons.light_mode : Icons.dark_mode,
),
);
}
buildThemeDebugger创建一个小型悬浮按钮,点击可以快速切换主题。图标根据当前模式动态变化。这个工具只在开发环境中显示,方便开发者测试不同主题下的界面效果。
总结
主题设置是现代应用的重要功能,它不仅影响外观,还关系到用户体验。通过合理的架构设计,我们可以实现灵活、高效的主题切换系统。
本文介绍了从基础的主题选择到高级的个性化配置的完整实现方案。关键点包括:使用GetX实现响应式状态管理、通过SharedPreferences持久化设置、提供实时预览效果、考虑可访问性和性能优化。
一个好的主题系统应该既简单易用,又功能丰富。通过本文的介绍,开发者可以为Flutter应用构建出优秀的主题切换功能,为用户提供个性化的使用体验。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐
所有评论(0)