Flutter for OpenHarmony 音乐播放器App实战 - 主题设置实现

前言

在音乐播放器应用中,主题设置是提升用户体验的重要功能。不同用户对界面风格有不同的偏好,有人喜欢活力四射的橙色,有人偏爱沉稳的深蓝。本篇我们将实现一个完整的主题设置页面,包含深色模式切换、多种主题色选择以及实时预览功能。

功能分析

在动手写代码之前,我们先梳理一下主题设置页面需要实现哪些功能。首先是显示模式的切换,用户可以选择深色模式或浅色模式,也可以设置跟随系统自动切换。其次是主题颜色的选择,我们提供8种预设的主题色供用户挑选。最后是预览功能,用户在选择主题色后可以立即看到效果,确认满意后再应用。

页面结构搭建

我们的主题设置页面采用 StatefulWidget 来实现,因为页面中有多个需要动态更新的状态。先来看页面的基础结构:

dart 复制代码
class ThemePage extends StatefulWidget {
  const ThemePage({super.key});

  @override
  State<ThemePage> createState() => _ThemePageState();
}

这里使用了 super.key 的简写语法,这是 Dart 2.17 引入的特性,让代码更加简洁。接下来定义页面的状态类,我们需要管理三个状态变量:当前选中的主题索引、深色模式开关状态、以及是否跟随系统。

dart 复制代码
class _ThemePageState extends State<ThemePage> {
  int _selectedTheme = 0;
  bool _darkMode = true;
  bool _followSystem = false;

_selectedTheme 用于记录用户选择的主题颜色索引,默认值为0表示选中第一个主题。_darkMode 控制深色模式的开关,默认开启。_followSystem 决定是否跟随系统的深浅色设置。

定义主题色数据

为了让代码更易维护,我们把所有可选的主题色定义在一个列表中:

dart 复制代码
  final List<Map<String, dynamic>> themes = [
    {'name': '经典粉', 'color': const Color(0xFFE91E63)},
    {'name': '活力橙', 'color': const Color(0xFFFF5722)},
    {'name': '清新绿', 'color': const Color(0xFF4CAF50)},
    {'name': '天空蓝', 'color': const Color(0xFF2196F3)},
    {'name': '神秘紫', 'color': const Color(0xFF9C27B0)},
    {'name': '土豪金', 'color': const Color(0xFFFFD700)},
    {'name': '深邃黑', 'color': const Color(0xFF424242)},
    {'name': '纯净白', 'color': const Color(0xFFFFFFFF)},
  ];

每个主题包含名称和对应的颜色值。使用 const Color 可以在编译期创建颜色对象,提升运行时性能。这8种颜色覆盖了大部分用户的审美偏好,从温暖的粉色到冷静的蓝色,从活泼的橙色到低调的黑色。

构建页面主体

页面主体使用 ListView 作为容器,内部包含三个主要区块:

dart 复制代码
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('主题设置')),
      body: ListView(
        padding: const EdgeInsets.all(16),
        children: [
          _buildModeSection(),
          const SizedBox(height: 24),
          _buildThemeSection(),
          const SizedBox(height: 24),
          _buildPreviewSection(),
        ],
      ),
    );
  }

ListView 自带滚动功能,当内容超出屏幕高度时用户可以滑动查看。三个区块之间使用 SizedBox 添加24像素的间距,让页面看起来不那么拥挤。

显示模式区块

显示模式区块包含两个开关选项,让用户控制深色模式和系统跟随:

dart 复制代码
  Widget _buildModeSection() {
    return Container(
      padding: const EdgeInsets.all(16),
      decoration: BoxDecoration(
        color: const Color(0xFF1E1E1E), 
        borderRadius: BorderRadius.circular(16)
      ),

外层 Container 设置了深灰色背景和圆角,与整体的深色主题保持一致。

dart 复制代码
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          const Text('显示模式', 
            style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
          const SizedBox(height: 16),
          SwitchListTile(
            title: const Text('深色模式'),
            subtitle: const Text('使用深色背景'),
            value: _darkMode,
            activeColor: themes[_selectedTheme]['color'],
            onChanged: _followSystem ? null : (v) => setState(() => _darkMode = v),
          ),

这里有个细节值得注意:当 _followSystem 为 true 时,onChanged 传入 null,这会让开关变成禁用状态。因为既然选择了跟随系统,手动切换深色模式就没有意义了。

dart 复制代码
          SwitchListTile(
            title: const Text('跟随系统'),
            subtitle: const Text('自动切换深色/浅色模式'),
            value: _followSystem,
            activeColor: themes[_selectedTheme]['color'],
            onChanged: (v) => setState(() => _followSystem = v),
          ),
        ],
      ),
    );
  }

跟随系统的开关始终可用,用户可以随时开启或关闭这个功能。activeColor 使用当前选中的主题色,让开关的颜色与整体主题协调。

主题颜色选择区块

这是整个页面的核心部分,使用网格布局展示所有可选的主题色:

dart 复制代码
  Widget _buildThemeSection() {
    return Container(
      padding: const EdgeInsets.all(16),
      decoration: BoxDecoration(
        color: const Color(0xFF1E1E1E), 
        borderRadius: BorderRadius.circular(16)
      ),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          const Text('主题颜色', 
            style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
          const SizedBox(height: 16),

容器样式与上一个区块保持一致,视觉上形成统一的卡片风格。

dart 复制代码
          GridView.builder(
            shrinkWrap: true,
            physics: const NeverScrollableScrollPhysics(),
            gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
              crossAxisCount: 4, 
              mainAxisSpacing: 16, 
              crossAxisSpacing: 16
            ),
            itemCount: themes.length,

GridView.builder 用于构建网格布局。shrinkWrap: true 让网格高度自适应内容,NeverScrollableScrollPhysics 禁用网格自身的滚动,因为外层已经有 ListView 处理滚动了。crossAxisCount: 4 表示每行显示4个主题色选项。

dart 复制代码
            itemBuilder: (context, index) {
              final theme = themes[index];
              final isSelected = _selectedTheme == index;
              return GestureDetector(
                onTap: () => setState(() => _selectedTheme = index),
                child: Column(
                  children: [
                    Container(
                      width: 50, height: 50,
                      decoration: BoxDecoration(
                        color: theme['color'],
                        shape: BoxShape.circle,
                        border: isSelected 
                          ? Border.all(color: Colors.white, width: 3) 
                          : null,
                        boxShadow: isSelected 
                          ? [BoxShadow(
                              color: (theme['color'] as Color).withOpacity(0.5), 
                              blurRadius: 10
                            )] 
                          : null,
                      ),
                      child: isSelected 
                        ? const Icon(Icons.check, color: Colors.white) 
                        : null,
                    ),

每个主题色用一个圆形容器展示。选中状态下会有白色边框、发光阴影和对勾图标,让用户清楚知道当前选择的是哪个主题。BoxShadow 的颜色使用主题色的半透明版本,营造出柔和的光晕效果。

dart 复制代码
                    const SizedBox(height: 4),
                    Text(theme['name'], 
                      style: TextStyle(
                        fontSize: 12, 
                        color: isSelected ? theme['color'] : Colors.grey
                      )
                    ),
                  ],
                ),
              );
            },
          ),
        ],
      ),
    );
  }

主题名称显示在圆形下方,选中时文字颜色与主题色一致,未选中时显示灰色。

预览效果区块

预览区块让用户在应用主题前先看到效果,这是提升用户体验的关键设计:

dart 复制代码
  Widget _buildPreviewSection() {
    final themeColor = themes[_selectedTheme]['color'] as Color;
    return Container(
      padding: const EdgeInsets.all(16),
      decoration: BoxDecoration(
        color: const Color(0xFF1E1E1E), 
        borderRadius: BorderRadius.circular(16)
      ),

首先获取当前选中的主题色,后续的预览组件都会使用这个颜色。

dart 复制代码
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          const Text('预览效果', 
            style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
          const SizedBox(height: 16),
          Container(
            padding: const EdgeInsets.all(16),
            decoration: BoxDecoration(
              color: const Color(0xFF121212), 
              borderRadius: BorderRadius.circular(12)
            ),

预览区使用更深的背景色,与外层容器形成层次感。

dart 复制代码
            child: Column(
              children: [
                Row(
                  children: [
                    Container(
                      width: 60, height: 60, 
                      decoration: BoxDecoration(
                        color: themeColor.withOpacity(0.3), 
                        borderRadius: BorderRadius.circular(8)
                      ), 
                      child: Icon(Icons.music_note, color: themeColor)
                    ),
                    const SizedBox(width: 12),
                    const Expanded(
                      child: Column(
                        crossAxisAlignment: CrossAxisAlignment.start, 
                        children: [
                          Text('示例歌曲', 
                            style: TextStyle(fontWeight: FontWeight.bold)),
                          Text('示例歌手', 
                            style: TextStyle(color: Colors.grey, fontSize: 12))
                        ]
                      )
                    ),
                    Icon(Icons.favorite, color: themeColor),
                  ],
                ),

预览区模拟了一个迷你播放器的样式,包含歌曲封面、歌曲信息和收藏按钮。封面背景使用主题色的30%透明度版本,图标和收藏按钮都使用主题色。

dart 复制代码
                const SizedBox(height: 16),
                LinearProgressIndicator(
                  value: 0.6, 
                  backgroundColor: Colors.grey.withOpacity(0.3), 
                  valueColor: AlwaysStoppedAnimation(themeColor)
                ),

进度条是展示主题色效果的绝佳位置,valueColor 使用 AlwaysStoppedAnimation 包装主题色,让进度条的已播放部分显示为主题色。

dart 复制代码
                const SizedBox(height: 16),
                Row(
                  mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                  children: [
                    const Icon(Icons.shuffle, color: Colors.grey),
                    const Icon(Icons.skip_previous),
                    Container(
                      width: 50, height: 50, 
                      decoration: BoxDecoration(
                        color: themeColor, 
                        shape: BoxShape.circle
                      ), 
                      child: const Icon(Icons.play_arrow, color: Colors.white)
                    ),
                    const Icon(Icons.skip_next),
                    const Icon(Icons.repeat, color: Colors.grey),
                  ],
                ),
              ],
            ),
          ),

播放控制栏中,播放按钮使用主题色作为背景,这是整个播放器界面最醒目的元素。其他控制按钮保持默认或灰色,突出主按钮的视觉重心。

dart 复制代码
          const SizedBox(height: 16),
          SizedBox(
            width: double.infinity,
            child: ElevatedButton(
              style: ElevatedButton.styleFrom(
                backgroundColor: themeColor, 
                padding: const EdgeInsets.symmetric(vertical: 14)
              ),
              onPressed: () => Get.back(),
              child: const Text('应用主题', style: TextStyle(color: Colors.white)),
            ),
          ),
        ],
      ),
    );
  }

最后是应用主题按钮,同样使用当前选中的主题色作为背景。width: double.infinity 让按钮撑满整个宽度,vertical: 14 的内边距让按钮有足够的点击区域。点击后调用 Get.back() 返回上一页,实际项目中这里应该先保存用户的主题选择。

页面入口

主题设置页面的入口在个人中心页面,用户点击"主题设置"菜单项即可进入:

dart 复制代码
{'icon': Icons.palette, 'label': '主题设置', 'page': () => const ThemePage()},

使用调色板图标 Icons.palette 直观地表达主题设置的含义。

小结

本篇我们实现了一个功能完整的主题设置页面,包含显示模式切换、8种主题色选择和实时预览功能。通过合理的状态管理和组件拆分,代码结构清晰易懂。预览功能的加入让用户在应用主题前就能看到效果,大大提升了使用体验。在实际项目中,还可以结合状态管理框架将主题设置持久化,让用户下次打开应用时自动应用上次的选择。


欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

相关推荐
小风呼呼吹儿3 小时前
Flutter 框架跨平台鸿蒙开发 - 虚拟红包雨应用开发教程
flutter·华为·harmonyos
石像鬼₧魂石3 小时前
内网渗透学习框架:五维金字塔
windows·学习
体面:4 小时前
2026闲鱼监控助手
windows·经验分享
2501_944521596 小时前
Flutter for OpenHarmony 微动漫App实战:主题配置实现
android·开发语言·前端·javascript·flutter·ecmascript
时光慢煮6 小时前
Flutter × OpenHarmony 跨端开发实战:动态显示菜单详解
flutter·华为·开源·openharmony
小雨青年6 小时前
环境准备 Windows Mac 下 Docker Desktop 的安装与镜像源加速
windows·macos·docker
2501_944521596 小时前
Flutter for OpenHarmony 微动漫App实战:动漫卡片组件实现
android·开发语言·javascript·flutter·ecmascript
晚霞的不甘7 小时前
Flutter 布局核心:构建交互式文档应用
开发语言·javascript·flutter·elasticsearch·正则表达式
数据知道7 小时前
一文掌握 MongoDB 详细安装与配置(Windows / Linux / macOS 全平台)
linux·数据库·windows·mongodb·macos