设置功能是应用的配置中心,用户可以在这里调整提醒设置、过期预警、数据管理等选项。本文将介绍如何在Flutter for OpenHarmony应用中实现设置功能,包括开关控制、选项选择、数据管理等特性。

功能设计思路

设置页面包含三个主要模块:提醒设置、过期提醒、数据管理。提醒设置包括开启提醒通知、提醒声音、震动提醒、提前提醒时间等选项。过期提醒可以设置过期预警天数。数据管理包括导出数据、导入数据、清除所有数据等功能。

页面采用列表布局,使用分组标题将不同模块分隔开。开关类选项使用Switch组件,选择类选项点击后弹出对话框。数据管理中的清除数据功能使用红色文字标识,提醒用户谨慎操作。

页面结构实现

页面使用有状态组件,维护各个设置项的状态:

class _SettingsScreenState extends State<SettingsScreen> {
  bool _notificationEnabled = true;
  bool _soundEnabled = true;
  bool _vibrationEnabled = true;
  int _reminderAdvanceMinutes = 5;
  int _expiryWarningDays = 30;

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('设置')),
      body: ListView(
        children: [
          _buildSectionHeader('提醒设置'),
          _buildSwitchTile(
            '开启提醒通知',
            '接收用药提醒推送通知',
            _notificationEnabled,
            (v) => setState(() => _notificationEnabled = v),
          ),
          _buildSwitchTile(
            '提醒声音',
            '提醒时播放声音',
            _soundEnabled,
            (v) => setState(() => _soundEnabled = v),
          ),
          _buildSwitchTile(
            '震动提醒',
            '提醒时震动',
            _vibrationEnabled,
            (v) => setState(() => _vibrationEnabled = v),
          ),
          _buildListTile(
            '提前提醒时间',
            '$_reminderAdvanceMinutes 分钟',
            () => _showAdvanceTimeDialog(),
          ),
          _buildSectionHeader('过期提醒'),
          _buildListTile(
            '过期预警天数',
            '$_expiryWarningDays 天',
            () => _showExpiryWarningDialog(),
          ),
          _buildSectionHeader('数据管理'),
          _buildListTile('导出数据', '导出药品和健康数据', () {}),
          _buildListTile('导入数据', '从备份文件导入', () {}),
          _buildListTile(
            '清除所有数据',
            '删除所有本地数据',
            () => _showClearDataDialog(),
            isDestructive: true,
          ),
        ],
      ),
    );
  }
}

页面使用ListView构建设置列表,包含多个分组和设置项。状态变量用于存储各个设置的值,通过setState更新UI。设置项分为开关类和选择类两种,分别使用不同的组件实现。

分组标题

分组标题用于分隔不同的设置模块:

Widget _buildSectionHeader(String title) {
  return Container(
    padding: EdgeInsets.fromLTRB(16.w, 16.h, 16.w, 8.h),
    child: Text(
      title,
      style: TextStyle(fontSize: 14.sp, color: Colors.grey[600], fontWeight: FontWeight.w500),
    ),
  );
}

分组标题使用灰色小号字体,与设置项区分开。上下有适当的间距,让分组更加清晰。这种设计让设置页面的结构一目了然。

开关类设置项

开关类设置项使用Switch组件实现:

Widget _buildSwitchTile(String title, String subtitle, bool value, ValueChanged<bool> onChanged) {
  return ListTile(
    title: Text(title),
    subtitle: Text(subtitle, style: TextStyle(fontSize: 12.sp, color: Colors.grey[500])),
    trailing: Switch(
      value: value,
      onChanged: onChanged,
      activeColor: const Color(0xFF00897B),
    ),
  );
}

开关设置项使用ListTile布局,标题和副标题在左侧,Switch开关在右侧。开关的激活颜色使用应用主题色,保持视觉统一。当用户切换开关时,通过回调函数更新状态。

选择类设置项

选择类设置项点击后弹出对话框:

Widget _buildListTile(String title, String subtitle, VoidCallback onTap, {bool isDestructive = false}) {
  return ListTile(
    title: Text(title, style: TextStyle(color: isDestructive ? Colors.red : null)),
    subtitle: Text(subtitle, style: TextStyle(fontSize: 12.sp, color: Colors.grey[500])),
    trailing: const Icon(Icons.chevron_right, color: Colors.grey),
    onTap: onTap,
  );
}

选择设置项也使用ListTile布局,右侧是右箭头提示可点击。isDestructive参数用于标识危险操作,如清除数据,这类操作的标题使用红色字体。点击设置项会触发onTap回调,通常是弹出对话框。

提前提醒时间选择

提前提醒时间通过单选对话框选择:

void _showAdvanceTimeDialog() {
  showDialog(
    context: context,
    builder: (context) => AlertDialog(
      title: const Text('提前提醒时间'),
      content: Column(
        mainAxisSize: MainAxisSize.min,
        children: [5, 10, 15, 30].map((minutes) {
          return RadioListTile<int>(
            title: Text('$minutes 分钟'),
            value: minutes,
            groupValue: _reminderAdvanceMinutes,
            onChanged: (v) {
              setState(() => _reminderAdvanceMinutes = v!);
              Navigator.pop(context);
            },
          );
        }).toList(),
      ),
    ),
  );
}

对话框包含四个选项:5分钟、10分钟、15分钟、30分钟。使用RadioListTile实现单选效果,当前选中的值通过groupValue指定。用户选择后立即更新状态并关闭对话框,体验流畅。

过期预警天数选择

过期预警天数的选择方式与提前提醒时间类似:

void _showExpiryWarningDialog() {
  showDialog(
    context: context,
    builder: (context) => AlertDialog(
      title: const Text('过期预警天数'),
      content: Column(
        mainAxisSize: MainAxisSize.min,
        children: [7, 14, 30, 60, 90].map((days) {
          return RadioListTile<int>(
            title: Text('$days 天'),
            value: days,
            groupValue: _expiryWarningDays,
            onChanged: (v) {
              setState(() => _expiryWarningDays = v!);
              Navigator.pop(context);
            },
          );
        }).toList(),
      ),
    ),
  );
}

对话框包含五个选项:7天、14天、30天、60天、90天。用户可以根据自己的需求选择合适的预警天数。选择后立即生效,系统会根据新的设置进行过期提醒。

清除数据确认

清除数据是危险操作,需要二次确认:

void _showClearDataDialog() {
  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);
            // 清除数据逻辑
          },
          child: const Text('确定', style: TextStyle(color: Colors.red)),
        ),
      ],
    ),
  );
}

确认对话框详细说明了操作的后果,提醒用户数据无法恢复。确定按钮使用红色字体,再次强调操作的危险性。这种设计可以有效防止用户误操作。

技术要点

状态管理:页面使用多个状态变量存储设置值,通过setState更新UI。这种简单的状态管理方式适合设置页面这种相对独立的功能。

组件封装:将分组标题、开关设置项、选择设置项封装成独立的方法,代码结构清晰,易于维护和扩展。

用户体验:开关类设置可以直接切换,选择类设置通过对话框选择,交互方式符合用户习惯。危险操作使用红色标识并二次确认,防止误操作。

对话框设计:选择对话框使用RadioListTile实现单选效果,选择后立即生效并关闭对话框,体验流畅。确认对话框详细说明操作后果,让用户做出明智的决定。

设置持久化存储

为了让设置在应用重启后依然生效,需要将设置值持久化存储到本地:

import 'package:shared_preferences/shared_preferences.dart';

Future<void> _loadSettings() async {
  final prefs = await SharedPreferences.getInstance();
  setState(() {
    _notificationEnabled = prefs.getBool('notification_enabled') ?? true;
    _soundEnabled = prefs.getBool('sound_enabled') ?? true;
    _vibrationEnabled = prefs.getBool('vibration_enabled') ?? true;
    _reminderAdvanceMinutes = prefs.getInt('reminder_advance_minutes') ?? 5;
    _expiryWarningDays = prefs.getInt('expiry_warning_days') ?? 30;
  });
}

initState中调用_loadSettings方法加载保存的设置值。使用SharedPreferences库实现键值对存储,每个设置项对应一个键名。如果没有保存过该设置,则使用默认值。这种设计确保了用户的设置偏好能够被记住。

保存设置变更

当用户修改设置时,需要同步保存到本地存储:

Future<void> _saveNotificationEnabled(bool value) async {
  final prefs = await SharedPreferences.getInstance();
  await prefs.setBool('notification_enabled', value);
  setState(() => _notificationEnabled = value);
}

Future<void> _saveReminderAdvanceMinutes(int minutes) async {
  final prefs = await SharedPreferences.getInstance();
  await prefs.setInt('reminder_advance_minutes', minutes);
  setState(() => _reminderAdvanceMinutes = minutes);
}

每个设置项都有对应的保存方法,先写入本地存储,再更新UI状态。这种方式确保了数据的一致性,即使应用意外退出,设置也不会丢失。开关类设置在切换时立即保存,选择类设置在对话框确认后保存。

导出数据功能实现

导出数据功能将应用数据导出为JSON文件,方便用户备份:

Future<void> _exportData() async {
  final medicines = context.read<MedicineProvider>().medicines;
  final members = context.read<FamilyProvider>().members;
  final reminders = context.read<ReminderProvider>().reminders;
  
  final exportData = {
    'version': '1.0',
    'exportTime': DateTime.now().toIso8601String(),
    'medicines': medicines.map((m) => m.toJson()).toList(),
    'members': members.map((m) => m.toJson()).toList(),
    'reminders': reminders.map((r) => r.toJson()).toList(),
  };
  
  final jsonString = jsonEncode(exportData);
  // 保存到文件或分享
  Get.snackbar('成功', '数据已导出', snackPosition: SnackPosition.BOTTOM);
}

导出功能从各个Provider获取数据,转换为JSON格式。导出文件包含版本号和导出时间,方便后续导入时进行版本兼容处理。用户可以将导出的文件保存到本地或通过分享功能发送到其他设备。

导入数据功能实现

导入数据功能从备份文件恢复数据:

Future<void> _importData(String jsonString) async {
  try {
    final data = jsonDecode(jsonString) as Map<String, dynamic>;
    final version = data['version'] as String;
    
    if (version == '1.0') {
      final medicines = (data['medicines'] as List)
          .map((m) => Medicine.fromJson(m))
          .toList();
      final members = (data['members'] as List)
          .map((m) => FamilyMember.fromJson(m))
          .toList();
      
      context.read<MedicineProvider>().importMedicines(medicines);
      context.read<FamilyProvider>().importMembers(members);
      
      Get.snackbar('成功', '数据已导入', snackPosition: SnackPosition.BOTTOM);
    }
  } catch (e) {
    Get.snackbar('错误', '导入失败:文件格式不正确', snackPosition: SnackPosition.BOTTOM);
  }
}

导入功能解析JSON文件,根据版本号进行相应的数据转换。使用try-catch捕获解析错误,向用户显示友好的错误提示。导入成功后,各个Provider会更新数据并通知UI刷新。

通知权限检查

开启提醒通知前需要检查系统通知权限:

Future<void> _checkNotificationPermission() async {
  // 检查通知权限状态
  final status = await Permission.notification.status;
  
  if (status.isDenied) {
    final result = await Permission.notification.request();
    if (result.isGranted) {
      _saveNotificationEnabled(true);
    } else {
      Get.snackbar(
        '权限提示',
        '请在系统设置中开启通知权限',
        snackPosition: SnackPosition.BOTTOM,
      );
    }
  } else if (status.isGranted) {
    _saveNotificationEnabled(true);
  }
}

当用户开启提醒通知时,首先检查系统通知权限。如果权限被拒绝,则请求权限。如果用户拒绝授权,显示提示引导用户到系统设置中手动开启。这种设计确保了通知功能能够正常工作。

设置项动画效果

为设置项添加切换动画,提升用户体验:

Widget _buildAnimatedSwitchTile(
  String title,
  String subtitle,
  bool value,
  ValueChanged<bool> onChanged,
) {
  return AnimatedContainer(
    duration: const Duration(milliseconds: 200),
    decoration: BoxDecoration(
      color: value ? const Color(0xFF00897B).withOpacity(0.05) : Colors.transparent,
    ),
    child: ListTile(
      title: Text(title),
      subtitle: Text(
        subtitle,
        style: TextStyle(fontSize: 12.sp, color: Colors.grey[500]),
      ),
      trailing: Switch(
        value: value,
        onChanged: onChanged,
        activeColor: const Color(0xFF00897B),
      ),
    ),
  );
}

使用AnimatedContainer为设置项添加背景色动画。当开关打开时,设置项背景会渐变为浅青色,提供视觉反馈。动画时长设置为200毫秒,既能让用户感知到变化,又不会显得拖沓。

版本信息展示

在设置页面底部展示应用版本信息:

Widget _buildVersionInfo() {
  return Padding(
    padding: EdgeInsets.symmetric(vertical: 24.h),
    child: Column(
      children: [
        Text(
          '家庭药箱管理',
          style: TextStyle(fontSize: 14.sp, color: Colors.grey[600]),
        ),
        SizedBox(height: 4.h),
        Text(
          'v1.0.0',
          style: TextStyle(fontSize: 12.sp, color: Colors.grey[400]),
        ),
      ],
    ),
  );
}

版本信息使用灰色小号字体,不抢夺视觉焦点。用户可以在这里查看当前应用版本,方便反馈问题时提供版本信息。这是设置页面的常见设计模式。

总结

设置功能通过清晰的分组和友好的交互设计,为用户提供了灵活的配置选项。从提醒设置到数据管理,涵盖了应用的各个方面。开关类设置可以快速切换,选择类设置通过对话框选择,危险操作有二次确认保护。这种设计让用户可以根据自己的需求定制应用,提升使用体验。


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

Logo

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

更多推荐