设置页面是应用的控制中心,用户可以在这里调整学习偏好、播放选项和通知方式。本文介绍如何实现一个功能完善的设置页面,包括开关控件、选择对话框和状态管理。

Provider状态管理

首先获取全局状态管理对象:

class SettingsScreen extends StatelessWidget {
  const SettingsScreen({super.key});

  
  Widget build(BuildContext context) {
    final userProvider = Provider.of<UserProvider>(context);
    final appProvider = Provider.of<AppProvider>(context);

使用Provider.of获取两个Provider实例,userProvider管理用户相关设置,appProvider管理应用全局配置。通过Provider实现状态共享,设置的修改会自动同步到其他页面。

页面整体布局

构建设置页面的基础结构:

    return Scaffold(
      appBar: AppBar(title: const Text('设置')),
      body: ListView(
        children: [
          _buildSection('学习设置', [
            _buildSwitchTile(
              '每日提醒',
              '开启后每天提醒你学习',
              userProvider.settings['dailyReminder'] ?? true,
              (value) => userProvider.updateSetting('dailyReminder', value),
            ),

使用ListView作为容器,因为设置项可能很多需要滚动。_buildSection方法构建分组,每个分组包含标题和多个设置项。开关类设置用_buildSwitchTile构建,传入标题、描述、当前值和回调函数。

每日目标设置

点击跳转到选择对话框:

            _buildListTile(
              '每日目标',
              '${appProvider.dailyGoal}个词汇',
              () => _showGoalDialog(context, appProvider),
            ),
            _buildListTile(
              '提醒时间',
              userProvider.settings['reminderTime'] ?? '09:00',
              () => _showTimePickerDialog(context, userProvider),
            ),
          ]),

_buildListTile构建可点击的列表项,右侧显示当前值。点击每日目标弹出选择对话框,点击提醒时间弹出时间选择器。这种二级交互避免在主页面放置过多控件,保持界面简洁。

播放设置分组

视频播放相关的设置:

          _buildSection('播放设置', [
            _buildSwitchTile(
              '自动播放',
              '进入课程自动播放演示',
              userProvider.settings['autoPlay'] ?? false,
              (value) => userProvider.updateSetting('autoPlay', value),
            ),
            _buildListTile(
              '播放速度',
              '${userProvider.settings['playbackSpeed'] ?? 1.0}x',
              () => _showSpeedDialog(context, userProvider),
            ),
          ]),

自动播放用开关控制,播放速度点击弹出选择对话框。将相关设置归为一组,用户能快速找到想要的功能。分组标题用灰色小字显示,与设置项形成视觉层次

通知设置分组

推送和反馈相关的设置:

          _buildSection('通知设置', [
            _buildSwitchTile(
              '推送通知',
              '接收学习提醒和活动通知',
              userProvider.settings['notifications'] ?? true,
              (value) => userProvider.updateSetting('notifications', value),
            ),
            _buildSwitchTile(
              '声音效果',
              '答题时播放音效',
              userProvider.settings['soundEffects'] ?? true,
              (value) => userProvider.updateSetting('soundEffects', value),
            ),
            _buildSwitchTile(
              '震动反馈',
              '操作时震动提示',
              userProvider.settings['vibration'] ?? true,
              (value) => userProvider.updateSetting('vibration', value),
            ),
          ]),

三个开关控制不同类型的反馈:推送通知、声音效果和震动反馈。每个开关都有描述文字,说明功能作用。用户可以根据个人喜好自由组合,比如只要震动不要声音。

显示设置分组

界面外观相关的设置:

          _buildSection('显示设置', [
            _buildListTile(
              '字体大小',
              _getFontSizeLabel(userProvider.settings['fontSize'] ?? 'medium'),
              () => _showFontSizeDialog(context, userProvider),
            ),
            _buildSwitchTile(
              '深色模式',
              '切换深色主题',
              appProvider.isDarkMode,
              (value) => appProvider.toggleDarkMode(),
            ),
          ]),

字体大小点击弹出选择对话框,深色模式用开关直接切换。_getFontSizeLabel方法将内部值转换为用户友好的文字,比如medium显示为"中"。深色模式的切换会立即生效,给用户即时反馈

其他设置分组

杂项功能的入口:

          _buildSection('其他', [
            _buildListTile('清除缓存', '释放存储空间', () => _showClearCacheDialog(context)),
            _buildListTile('隐私政策', '', () {}),
            _buildListTile('用户协议', '', () {}),
          ]),

清除缓存点击弹出确认对话框,隐私政策和用户协议点击跳转到对应页面(这里暂时空实现)。将不常用的功能归为"其他"分组,避免干扰主要设置项。

退出登录按钮

页面底部的危险操作:

          SizedBox(height: 20.h),
          Padding(
            padding: EdgeInsets.symmetric(horizontal: 16.w),
            child: OutlinedButton(
              onPressed: () => _showLogoutDialog(context),
              style: OutlinedButton.styleFrom(
                foregroundColor: Colors.red,
                side: const BorderSide(color: Colors.red),
              ),
              child: const Text('退出登录'),
            ),
          ),
          SizedBox(height: 20.h),
        ],
      ),
    );
  }

退出登录按钮用红色边框和文字,表示这是危险操作。放在页面最底部,避免用户误触。点击弹出确认对话框,二次确认防止误操作。上下留出20.h的间距,让按钮不会紧贴其他内容。

分组构建方法

封装分组的构建逻辑:

  Widget _buildSection(String title, List<Widget> children) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Padding(
          padding: EdgeInsets.fromLTRB(16.w, 16.h, 16.w, 8.h),
          child: Text(title, style: TextStyle(fontSize: 14.sp, color: Colors.grey, fontWeight: FontWeight.w500)),
        ),
        Card(margin: EdgeInsets.symmetric(horizontal: 16.w), child: Column(children: children)),
      ],
    );
  }

标题用灰色小字显示,设置项用Card包裹形成视觉分组。crossAxisAlignment.start让标题左对齐,fromLTRB精确控制标题的内边距。这种模块化设计让代码更易维护,添加新分组只需调用一次方法。

开关列表项

构建带开关的设置项:

  Widget _buildSwitchTile(String title, String subtitle, bool value, Function(bool) onChanged) {
    return SwitchListTile(
      title: Text(title),
      subtitle: subtitle.isNotEmpty ? Text(subtitle, style: TextStyle(fontSize: 12.sp, color: Colors.grey)) : null,
      value: value,
      onChanged: onChanged,
      activeColor: const Color(0xFF00897B),
    );
  }

SwitchListTile是Flutter提供的组合组件,包含标题、副标题和开关。subtitle为空时不显示,避免多余空白。activeColor设为主题色,开关打开时显示青绿色。回调函数onChanged接收新值,调用Provider更新状态。

普通列表项

构建可点击的设置项:

  Widget _buildListTile(String title, String trailing, VoidCallback onTap) {
    return ListTile(
      title: Text(title),
      trailing: Row(
        mainAxisSize: MainAxisSize.min,
        children: [
          if (trailing.isNotEmpty) Text(trailing, style: TextStyle(color: Colors.grey, fontSize: 14.sp)),
          const Icon(Icons.chevron_right, color: Colors.grey),
        ],
      ),
      onTap: onTap,
    );
  }

右侧用Row横向排列当前值和箭头图标。mainAxisSize.min让Row只占用必要空间,不会撑满整行。if (trailing.isNotEmpty)条件渲染,值为空时只显示箭头。箭头图标提示用户这是可点击的。

字体大小标签转换

将内部值转换为显示文字:

  String _getFontSizeLabel(String size) {
    switch (size) {
      case 'small': return '小';
      case 'medium': return '中';
      case 'large': return '大';
      default: return '中';
    }
  }

内部用英文存储,显示时转换为中文。这种数据与展示分离的做法便于国际化,只需修改转换方法就能支持多语言。default分支处理未知值,提高代码健壮性。

每日目标对话框

弹出选择对话框:

  void _showGoalDialog(BuildContext context, AppProvider appProvider) {
    showDialog(
      context: context,
      builder: (context) => AlertDialog(
        title: const Text('设置每日目标'),
        content: Column(
          mainAxisSize: MainAxisSize.min,
          children: [5, 10, 15, 20, 30].map((goal) {
            return ListTile(
              title: Text('$goal个词汇'),
              trailing: appProvider.dailyGoal == goal ? const Icon(Icons.check, color: Color(0xFF00897B)) : null,
              onTap: () { appProvider.setDailyGoal(goal); Navigator.pop(context); },
            );
          }).toList(),
        ),
      ),
    );
  }

AlertDialog显示对话框,contentColumn纵向排列选项。mainAxisSize.min让对话框高度自适应内容。当前选中的选项右侧显示对勾图标,点击选项后调用Provider更新值并关闭对话框。这种单选交互简单直观。

时间选择器

调用系统时间选择器:

  void _showTimePickerDialog(BuildContext context, UserProvider userProvider) async {
    final time = await showTimePicker(context: context, initialTime: TimeOfDay.now());
    if (time != null) {
      userProvider.updateSetting('reminderTime', '${time.hour.toString().padLeft(2, '0')}:${time.minute.toString().padLeft(2, '0')}');
    }
  }

showTimePicker是Flutter提供的系统时间选择器,返回TimeOfDay对象。await等待用户选择,if (time != null)判断用户是否取消。padLeft(2, '0')将小时和分钟格式化为两位数,比如9:5格式化为09:05。使用系统组件保持平台一致性

播放速度对话框

选择视频播放速度:

  void _showSpeedDialog(BuildContext context, UserProvider userProvider) {
    showDialog(
      context: context,
      builder: (context) => AlertDialog(
        title: const Text('播放速度'),
        content: Column(
          mainAxisSize: MainAxisSize.min,
          children: [0.5, 0.75, 1.0, 1.25, 1.5, 2.0].map((speed) {
            return ListTile(
              title: Text('${speed}x'),
              trailing: userProvider.settings['playbackSpeed'] == speed ? const Icon(Icons.check, color: Color(0xFF00897B)) : null,
              onTap: () { userProvider.updateSetting('playbackSpeed', speed); Navigator.pop(context); },
            );
          }).toList(),
        ),
      ),
    );
  }

提供6个速度选项,从0.5倍到2倍。当前速度右侧显示对勾,点击更新设置并关闭对话框。播放速度是视频学习的常用功能,有些用户喜欢慢速反复观看,有些用户喜欢快速浏览。

字体大小对话框

选择界面字体大小:

  void _showFontSizeDialog(BuildContext context, UserProvider userProvider) {
    showDialog(
      context: context,
      builder: (context) => AlertDialog(
        title: const Text('字体大小'),
        content: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            {'value': 'small', 'label': '小'},
            {'value': 'medium', 'label': '中'},
            {'value': 'large', 'label': '大'},
          ].map((item) {
            return ListTile(
              title: Text(item['label']!),
              trailing: userProvider.settings['fontSize'] == item['value'] ? const Icon(Icons.check, color: Color(0xFF00897B)) : null,
              onTap: () { userProvider.updateSetting('fontSize', item['value']); Navigator.pop(context); },
            );
          }).toList(),
        ),
      ),
    );
  }

提供小、中、大三个选项,用Map存储内部值和显示文字的对应关系。这种设计便于扩展,比如添加"特大"选项只需在数组中增加一项。字体大小影响可读性,特别是对视力不好的用户很重要。

清除缓存对话框

确认危险操作:

  void _showClearCacheDialog(BuildContext context) {
    showDialog(
      context: context,
      builder: (context) => AlertDialog(
        title: const Text('清除缓存'),
        content: const Text('确定要清除所有缓存数据吗?'),
        actions: [
          TextButton(onPressed: () => Navigator.pop(context), child: const Text('取消')),
          ElevatedButton(
            onPressed: () { Navigator.pop(context); ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('缓存已清除'))); },
            child: const Text('确定'),
          ),
        ],
      ),
    );
  }

对话框包含标题、内容和两个按钮。取消按钮用TextButton样式,确定按钮用ElevatedButton样式,视觉上更突出。点击确定后关闭对话框并显示SnackBar提示操作成功。这种二次确认防止用户误操作。

退出登录对话框

确认退出操作:

  void _showLogoutDialog(BuildContext context) {
    showDialog(
      context: context,
      builder: (context) => AlertDialog(
        title: const Text('退出登录'),
        content: const Text('确定要退出登录吗?'),
        actions: [
          TextButton(onPressed: () => Navigator.pop(context), child: const Text('取消')),
          ElevatedButton(
            onPressed: () => Navigator.pop(context),
            style: ElevatedButton.styleFrom(backgroundColor: Colors.red),
            child: const Text('退出', style: TextStyle(color: Colors.white)),
          ),
        ],
      ),
    );
  }
}

退出按钮用红色背景,强调这是危险操作。实际项目中点击确定后应该清除用户数据并跳转到登录页,这里只是演示对话框的使用。红色按钮配白色文字,对比度高更醒目。

Provider的优势

使用Provider管理状态:

final userProvider = Provider.of<UserProvider>(context);
userProvider.updateSetting('dailyReminder', value);

Provider实现响应式更新,调用updateSetting后所有监听该Provider的组件都会自动重建。不需要手动调用setState,也不需要传递回调函数,大大简化了状态管理逻辑。

空值处理

使用??运算符提供默认值:

userProvider.settings['dailyReminder'] ?? true
userProvider.settings['reminderTime'] ?? '09:00'
appProvider.dailyGoal

??运算符在左侧为null时返回右侧的默认值。这样即使设置项不存在,也能正常显示。dailyGoal直接访问属性,因为Provider内部已经处理了默认值。

对话框的复用

多个对话框使用相同的结构:

AlertDialog(
  title: const Text('...'),
  content: Column(
    mainAxisSize: MainAxisSize.min,
    children: [...].map((item) {
      return ListTile(...);
    }).toList(),
  ),
)

标题、内容、选项列表的结构都一样,只是数据不同。这种模式复用让用户形成操作习惯,看到这种对话框就知道是单选。也可以进一步封装成通用方法,减少重复代码。

小结

设置页面通过分组组织大量配置项,开关和选择对话框提供灵活的交互方式。Provider实现状态管理,设置的修改会自动同步到全局。危险操作用红色标识并二次确认,细节设计提升用户体验。整体布局清晰,功能完善,是应用不可或缺的一部分。


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

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐