在这里插入图片描述

说起设置页面,很多人觉得这是个不起眼的功能,无非就是几个开关和选项。但我自己做过几个App后发现,设置页面其实很重要。它是用户个性化应用的入口,也是很多高级功能的藏身之处。一个好的设置页面,能让用户用得更舒服。

为什么设置功能不可或缺

在开始写代码之前,我先想清楚了设置页面的价值。

第一个是个性化需求。每个人的使用习惯不一样,有人喜欢深色模式,有人喜欢浅色模式;有人喜欢声音提醒,有人觉得吵。设置页面就是让用户按自己的喜好调整应用。

第二个是功能开关。有些功能不是所有人都需要,比如推送通知。有人觉得很有用,有人觉得很烦。通过设置页面,用户可以自己决定开启还是关闭。

第三个是账户管理。修改密码、清除缓存、退出登录这些操作,都需要一个入口。设置页面就是这个入口。

功能设计的思路

在设计这个功能时,我考虑了以下几个方面。

分组管理

设置项很多,如果全部堆在一起,会很乱。我把设置项分成了几组:通知设置、外观设置、账户设置、其他。每组有个标题,看起来更清晰。

开关和选项的区分

有些设置是开关型的,比如"推送通知",只有开和关两种状态。有些设置是选项型的,比如"字体大小",有小、中、大三种选择。这两种设置的交互方式不同,要分别处理。

危险操作的警示

"退出登录"是个危险操作,点错了可能会丢失数据。所以我用红色按钮,放在最下面,和其他设置区分开。

页面整体结构

先看页面的基本框架:

class SettingsPage extends StatefulWidget {
  const SettingsPage({super.key});

  
  State<SettingsPage> createState() => _SettingsPageState();
}

class _SettingsPageState extends State<SettingsPage> {
  bool notificationEnabled = true;
  bool darkModeEnabled = false;
  bool soundEnabled = true;

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('设置'),
      ),
      body: ListView(
        children: [
          _buildSection('通知设置'),
          _buildSwitchTile('推送通知', '接收应用推送通知', Icons.notifications, notificationEnabled, (value) => setState(() => notificationEnabled = value)),
          _buildSwitchTile('声音提醒', '开启声音提醒', Icons.volume_up, soundEnabled, (value) => setState(() => soundEnabled = value)),
          _buildSection('外观设置'),
          _buildSwitchTile('深色模式', '开启深色主题', Icons.dark_mode, darkModeEnabled, (value) => setState(() => darkModeEnabled = value)),
          _buildTile('字体大小', '中', Icons.text_fields, () {}),
          _buildSection('账户设置'),
          _buildTile('修改密码', '', Icons.lock, () {}),
          _buildTile('清除缓存', '125 MB', Icons.cleaning_services, () {}),
          _buildSection('其他'),
          _buildTile('隐私政策', '', Icons.privacy_tip, () {}),
          _buildTile('用户协议', '', Icons.description, () {}),
          SizedBox(height: 20.h),
          _buildLogoutButton(),
          SizedBox(height: 20.h),
        ],
      ),
    );
  }
}

StatefulWidget的必要性

设置页面用了StatefulWidget,因为开关的状态会变化。用户点击开关,界面要更新。如果用StatelessWidget,就没法响应用户的操作。

状态变量的设计

定义了三个布尔变量,对应三个开关:

  • notificationEnabled:推送通知是否开启
  • darkModeEnabled:深色模式是否开启
  • soundEnabled:声音提醒是否开启

这些变量的初始值根据实际需求设置。比如推送通知默认开启,深色模式默认关闭。

ListView的使用

设置项用ListView展示,这样内容超出屏幕时可以滚动。不用Column+SingleChildScrollView,因为ListView更适合这种场景。

分组标题的设计

每组设置项上方有个标题,用灰色小字显示:

Widget _buildSection(String title) {
  return Padding(
    padding: EdgeInsets.fromLTRB(16.w, 20.h, 16.w, 8.h),
    child: Text(
      title,
      style: TextStyle(
        fontSize: 14.sp,
        color: Colors.grey,
        fontWeight: FontWeight.bold,
      ),
    ),
  );
}

Padding的设置

标题的上边距是20,下边距是8,左右边距是16。这样标题和上一组设置项有足够的间隔,和下一组设置项又不会太远。

文字样式的选择

标题用14号字,灰色,加粗。灰色表示这是辅助信息,不是主要内容。加粗让标题更醒目,和设置项区分开。

开关型设置项的实现

开关型设置项是最常见的,比如推送通知、深色模式:

Widget _buildSwitchTile(
  String title,
  String subtitle,
  IconData icon,
  bool value,
  Function(bool) onChanged,
) {
  return Container(
    margin: EdgeInsets.symmetric(horizontal: 16.w, vertical: 4.h),
    decoration: BoxDecoration(
      color: Colors.white,
      borderRadius: BorderRadius.circular(12.r),
    ),
    child: ListTile(
      leading: Icon(icon, color: Colors.blue),
      title: Text(title),
      subtitle: Text(subtitle, style: TextStyle(fontSize: 12.sp)),
      trailing: Switch(
        value: value,
        onChanged: onChanged,
      ),
    ),
  );
}

ListTile的使用

ListTile是Flutter提供的标准列表项组件,包含leading、title、subtitle、trailing四个部分。用它来构建设置项,代码很简洁。

leading图标的设计

每个设置项左边有个图标,用蓝色显示。图标能让用户快速识别设置项的类型,比如看到铃铛图标就知道是通知设置。

title和subtitle的层次

title是设置项的名称,用默认大小的字体。subtitle是设置项的说明,用12号小字,灰色。这种层次关系让信息更清晰。

Switch的交互

右边是一个开关,用户点击可以切换状态。value是当前状态,onChanged是状态改变时的回调。

在回调里调用setState,更新状态变量。这样开关的视觉状态就会跟着变化。

Container的装饰

每个设置项用白色卡片包裹,圆角12。上下边距4,左右边距16。这样设置项之间有间隔,不会挤在一起。

选项型设置项的实现

选项型设置项右边不是开关,而是当前选中的值和箭头:

Widget _buildTile(String title, String trailing, IconData icon, VoidCallback onTap) {
  return Container(
    margin: EdgeInsets.symmetric(horizontal: 16.w, vertical: 4.h),
    decoration: BoxDecoration(
      color: Colors.white,
      borderRadius: BorderRadius.circular(12.r),
    ),
    child: ListTile(
      leading: Icon(icon, color: Colors.blue),
      title: Text(title),
      trailing: Row(
        mainAxisSize: MainAxisSize.min,
        children: [
          if (trailing.isNotEmpty)
            Text(
              trailing,
              style: TextStyle(fontSize: 14.sp, color: Colors.grey),
            ),
          SizedBox(width: 8.w),
          const Icon(Icons.chevron_right, color: Colors.grey),
        ],
      ),
      onTap: onTap,
    ),
  );
}

trailing的动态显示

trailing参数是当前选中的值,比如"中"表示字体大小是中号。如果trailing不为空,就显示这个值;如果为空,就只显示箭头。

箭头的含义

右边的箭头表示这个设置项可以点击,点击后会进入详细页面或弹出选择器。这是一种常见的交互暗示。

onTap的处理

点击设置项时,调用onTap回调。在回调里可以跳转到详细页面,或者弹出对话框让用户选择。

比如点击"字体大小",可以弹出一个对话框,让用户选择小、中、大:

void _showFontSizeDialog() {
  showDialog(
    context: context,
    builder: (context) => AlertDialog(
      title: const Text('选择字体大小'),
      content: Column(
        mainAxisSize: MainAxisSize.min,
        children: [
          RadioListTile(
            title: const Text('小'),
            value: 'small',
            groupValue: _fontSize,
            onChanged: (value) {
              setState(() => _fontSize = value as String);
              Navigator.pop(context);
            },
          ),
          RadioListTile(
            title: const Text('中'),
            value: 'medium',
            groupValue: _fontSize,
            onChanged: (value) {
              setState(() => _fontSize = value as String);
              Navigator.pop(context);
            },
          ),
          RadioListTile(
            title: const Text('大'),
            value: 'large',
            groupValue: _fontSize,
            onChanged: (value) {
              setState(() => _fontSize = value as String);
              Navigator.pop(context);
            },
          ),
        ],
      ),
    ),
  );
}

清除缓存的实现

"清除缓存"这个设置项比较特殊,右边显示的是缓存大小:

_buildTile('清除缓存', '125 MB', Icons.cleaning_services, () {
  _showClearCacheDialog();
})

点击后,应该弹出确认对话框,询问用户是否确定清除:

void _showClearCacheDialog() {
  showDialog(
    context: context,
    builder: (context) => AlertDialog(
      title: const Text('清除缓存'),
      content: const Text('确定要清除所有缓存吗?此操作不可恢复。'),
      actions: [
        TextButton(
          onPressed: () => Navigator.pop(context),
          child: const Text('取消'),
        ),
        TextButton(
          onPressed: () {
            // 清除缓存的逻辑
            Navigator.pop(context);
            ScaffoldMessenger.of(context).showSnackBar(
              const SnackBar(content: Text('缓存已清除')),
            );
          },
          style: TextButton.styleFrom(foregroundColor: Colors.red),
          child: const Text('清除'),
        ),
      ],
    ),
  );
}

确认对话框的必要性

清除缓存是不可逆的操作,必须让用户确认。对话框的内容要明确告诉用户操作的后果。

清除按钮的颜色

清除按钮用红色,警示用户这是危险操作。和取消按钮的默认颜色形成对比。

操作反馈

清除完成后,用SnackBar显示提示:“缓存已清除”。这样用户知道操作成功了。

退出登录按钮的设计

退出登录按钮放在最下面,用红色,很醒目:

Widget _buildLogoutButton() {
  return Padding(
    padding: EdgeInsets.symmetric(horizontal: 16.w),
    child: ElevatedButton(
      onPressed: () {
        _showLogoutDialog();
      },
      style: ElevatedButton.styleFrom(
        backgroundColor: Colors.red,
        padding: EdgeInsets.symmetric(vertical: 16.h),
        shape: RoundedRectangleBorder(
          borderRadius: BorderRadius.circular(12.r),
        ),
      ),
      child: Text(
        '退出登录',
        style: TextStyle(fontSize: 16.sp, color: Colors.white),
      ),
    ),
  );
}

红色的警示作用

退出登录是个重要操作,用红色按钮能引起用户注意。红色在视觉上有警示作用,让用户知道这不是普通操作。

按钮的大小

按钮的垂直padding是16,比普通按钮大一些。这样按钮更醒目,也更容易点击。

确认对话框

点击退出登录后,应该弹出确认对话框:

void _showLogoutDialog() {
  showDialog(
    context: context,
    builder: (context) => AlertDialog(
      title: const Text('退出登录'),
      content: const Text('确定要退出登录吗?'),
      actions: [
        TextButton(
          onPressed: () => Navigator.pop(context),
          child: const Text('取消'),
        ),
        TextButton(
          onPressed: () {
            // 退出登录的逻辑
            Navigator.pop(context);
            // 跳转到登录页面
          },
          style: TextButton.styleFrom(foregroundColor: Colors.red),
          child: const Text('退出'),
        ),
      ],
    ),
  );
}

深色模式的实现

深色模式是个很受欢迎的功能,实现起来需要配合主题管理。

主题的切换

MaterialApp里定义两套主题:

MaterialApp(
  theme: ThemeData.light(),
  darkTheme: ThemeData.dark(),
  themeMode: _darkModeEnabled ? ThemeMode.dark : ThemeMode.light,
  // ...
)

当用户切换深色模式开关时,更新_darkModeEnabled变量,整个应用的主题就会跟着变化。

状态的持久化

用户的设置应该保存到本地,这样下次打开应用时,设置还在。可以用SharedPreferences

Future<void> _saveDarkMode(bool value) async {
  final prefs = await SharedPreferences.getInstance();
  await prefs.setBool('darkMode', value);
}

Future<void> _loadDarkMode() async {
  final prefs = await SharedPreferences.getInstance();
  setState(() {
    _darkModeEnabled = prefs.getBool('darkMode') ?? false;
  });
}

initState里调用_loadDarkMode,加载保存的设置。在开关的onChanged里调用_saveDarkMode,保存新的设置。

通知权限的处理

推送通知需要用户授权,不能直接开启。应该先检查权限,如果没有权限,引导用户去设置:

Future<void> _toggleNotification(bool value) async {
  if (value) {
    // 检查通知权限
    final status = await Permission.notification.status;
    if (status.isDenied) {
      // 请求权限
      final result = await Permission.notification.request();
      if (result.isDenied) {
        // 用户拒绝了权限
        ScaffoldMessenger.of(context).showSnackBar(
          const SnackBar(content: Text('需要通知权限才能开启推送')),
        );
        return;
      }
    }
  }
  
  setState(() {
    notificationEnabled = value;
  });
  await _saveNotificationSetting(value);
}

这样处理更规范,避免用户开启了推送但没有权限,导致收不到通知。

实际使用体验

这个设置页面我自己用了一段时间,感觉很方便。想调整什么设置,一眼就能找到。

分组管理很清晰,通知相关的设置在一起,外观相关的设置在一起,不会乱。

开关的交互很流畅,点一下就切换了,不需要额外的确认步骤。选项型设置点击后弹出选择器,也很直观。

退出登录按钮用红色,很醒目。我有次想退出登录,一眼就看到了,不用在菜单里找。

可以改进的地方

如果要做得更完善,可以考虑以下几点。

搜索功能

设置项多了以后,可以加个搜索功能。用户输入关键词,快速找到想要的设置项。

设置的分类更细

可以把设置分成更多类别,比如"隐私设置"、“数据设置”、"显示设置"等。每个类别单独一个页面,设置页面只显示类别入口。

设置的说明更详细

有些设置比较复杂,可以加个问号图标,点击后显示详细说明。这样用户能更好地理解每个设置的作用。

设置的预设方案

提供几套预设方案,比如"省电模式"、“性能模式”、“平衡模式”。用户可以一键切换,不用逐个调整设置。

设置的导入导出

用户可能在多个设备上使用,设置应该能同步。可以加个导入导出功能,把设置导出成文件,在其他设备上导入。

小结

今天实现了设置功能,用到了开关、列表、对话框等组件。核心是用分组管理设置项,用开关和选项两种交互方式,让用户能方便地调整应用。

这个功能虽然不是最核心的,但对用户体验影响很大。一个好的设置页面,能让用户用得更舒服,更符合自己的习惯。

在实现过程中,我特别注重信息的组织。分组标题、图标、说明文字,这些细节都是为了让用户快速找到想要的设置。危险操作用红色警示,也是为了避免用户误操作。

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

Logo

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

更多推荐