flutter_for_openharmony家庭药箱管理app实战+用药提醒列表实现

用药提醒列表是帮助用户管理所有提醒的中心页面。用户可以查看所有提醒的状态,通过开关快速启用或停用提醒,还可以查看历史记录。

功能设计思路

提醒列表展示所有用药提醒,每个提醒卡片包含药品名称、服药人、用量、提醒时间和状态开关。使用不同颜色区分启用和停用状态,提供直观的视觉反馈。

页面结构实现

列表页面使用StatelessWidget:

dart 复制代码
@override
Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(
      title: const Text('用药提醒'),
      actions: [
        IconButton(
          icon: const Icon(Icons.history),
          onPressed: () => Get.to(() => const ReminderHistoryScreen()),
        ),
      ],
    ),
    body: Consumer<ReminderProvider>(
      builder: (context, provider, child) {
        final reminders = provider.reminders;

        if (reminders.isEmpty) {
          return Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                Icon(Icons.alarm_off, size: 64.sp, color: Colors.grey[300]),
                SizedBox(height: 16.h),
                Text('暂无用药提醒', style: TextStyle(color: Colors.grey[500], fontSize: 16.sp)),
                SizedBox(height: 16.h),
                ElevatedButton(
                  onPressed: () => Get.to(() => const AddReminderScreen()),
                  child: const Text('添加提醒'),
                ),
              ],
            ),
          );
        }

        return ListView.builder(
          padding: EdgeInsets.all(16.w),
          itemCount: reminders.length,
          itemBuilder: (context, index) {
            final reminder = reminders[index];
            return _buildReminderCard(context, reminder, provider);
          },
        );
      },
    ),
    floatingActionButton: FloatingActionButton(
      onPressed: () => Get.to(() => const AddReminderScreen()),
      backgroundColor: const Color(0xFF00897B),
      child: const Icon(Icons.add),
    ),
  );
}

AppBar右侧提供历史记录入口,让用户可以查看过往的服药记录。使用Consumer监听Provider,当提醒数据变化时自动更新列表。

提醒卡片设计

每个提醒卡片分为两部分:

dart 复制代码
Widget _buildReminderCard(BuildContext context, dynamic reminder, ReminderProvider provider) {
  return Container(
    margin: EdgeInsets.only(bottom: 12.h),
    decoration: BoxDecoration(
      color: Colors.white,
      borderRadius: BorderRadius.circular(12.r),
      boxShadow: [
        BoxShadow(color: Colors.black.withOpacity(0.05), blurRadius: 10, offset: const Offset(0, 2)),
      ],
    ),
    child: Column(
      children: [
        Padding(
          padding: EdgeInsets.all(12.w),
          child: Row(
            children: [
              Container(
                width: 48.w,
                height: 48.w,
                decoration: BoxDecoration(
                  color: reminder.isActive
                      ? const Color(0xFF00897B).withOpacity(0.1)
                      : Colors.grey.withOpacity(0.1),
                  borderRadius: BorderRadius.circular(12.r),
                ),
                child: Icon(
                  Icons.medication,
                  color: reminder.isActive ? const Color(0xFF00897B) : Colors.grey,
                ),
              ),
              SizedBox(width: 12.w),
              Expanded(
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text(
                      reminder.medicineName,
                      style: TextStyle(
                        fontSize: 16.sp,
                        fontWeight: FontWeight.bold,
                        color: reminder.isActive ? Colors.black : Colors.grey,
                      ),
                    ),
                    SizedBox(height: 4.h),
                    Text(
                      '${reminder.memberName} · ${reminder.dosage}',
                      style: TextStyle(fontSize: 12.sp, color: Colors.grey[600]),
                    ),
                  ],
                ),
              ),
              Switch(
                value: reminder.isActive,
                onChanged: (value) {
                  provider.toggleReminderActive(reminder.id);
                },
                activeColor: const Color(0xFF00897B),
              ),
            ],
          ),
        ),
        Container(
          padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 8.h),
          decoration: BoxDecoration(
            color: Colors.grey[50],
            borderRadius: BorderRadius.only(
              bottomLeft: Radius.circular(12.r),
              bottomRight: Radius.circular(12.r),
            ),
          ),
          child: Row(
            children: [
              Icon(Icons.access_time, size: 16.sp, color: Colors.grey[600]),
              SizedBox(width: 4.w),
              Expanded(
                child: Text(
                  reminder.reminderTimes
                      .map((t) => DateFormat('HH:mm').format(t))
                      .join(' · '),
                  style: TextStyle(fontSize: 12.sp, color: Colors.grey[600]),
                ),
              ),
              Text(
                reminder.frequency,
                style: TextStyle(fontSize: 12.sp, color: const Color(0xFF00897B)),
              ),
            ],
          ),
        ),
      ],
    ),
  );
}

上半部分:显示药品图标、名称、服药人、用量和状态开关。图标和文字颜色根据启用状态动态变化,停用时使用灰色。

下半部分:显示提醒时间和频率。使用浅灰色背景区分,时间使用点号分隔,频率使用主题色突出显示。

状态开关

Switch组件控制提醒启用状态:

dart 复制代码
Switch(
  value: reminder.isActive,
  onChanged: (value) {
    provider.toggleReminderActive(reminder.id);
  },
  activeColor: const Color(0xFF00897B),
)

用户点击开关时,调用Provider的toggleReminderActive方法切换状态。Provider会通知所有监听者更新UI,卡片的颜色和文字会立即改变。

时间格式化

提醒时间使用DateFormat格式化:

dart 复制代码
reminder.reminderTimes
    .map((t) => DateFormat('HH:mm').format(t))
    .join(' · ')

将DateTime列表转换为"HH:mm"格式的字符串,使用点号连接多个时间。这种格式清晰易读,用户一眼就能看出所有提醒时间。

Provider提醒管理

Provider提供提醒管理方法:

dart 复制代码
class ReminderProvider extends ChangeNotifier {
  final List<MedicineReminder> _reminders = [];

  List<MedicineReminder> get reminders => _reminders;

  void toggleReminderActive(String id) {
    final index = _reminders.indexWhere((r) => r.id == id);
    if (index != -1) {
      _reminders[index] = _reminders[index].copyWith(
        isActive: !_reminders[index].isActive,
      );
      notifyListeners();
    }
  }

  void addReminder(MedicineReminder reminder) {
    _reminders.add(reminder);
    notifyListeners();
  }
}

toggleReminderActive方法找到对应提醒,使用copyWith创建新对象并切换isActive状态。这种不可变数据的设计让状态管理更加可靠。

空状态设计

空状态提示包含图标、文字和添加按钮。使用闹钟关闭图标,暗示当前没有提醒。这种设计让空状态成为引导用户添加提醒的入口。

颜色语义化

启用状态使用主题色(青绿色),表示正常工作。停用状态使用灰色,表示暂停。这种颜色编码让用户能够快速识别提醒状态,无需阅读文字。

卡片分层设计

卡片分为上下两层,上层是主要信息,下层是时间详情。使用不同背景色区分,让信息层次更加清晰。这种设计在保持紧凑的同时,提供了完整的信息展示。

响应式更新

使用Consumer监听Provider,当提醒状态变化时,卡片会自动更新颜色和文字。比如用户关闭提醒后,图标和文字立即变为灰色,提供即时的视觉反馈。

提醒搜索功能

当提醒数量较多时,搜索功能帮助用户快速找到目标提醒:

dart 复制代码
class _ReminderListScreenState extends State<ReminderListScreen> {
  String _searchQuery = '';
  final TextEditingController _searchController = TextEditingController();

  List<MedicineReminder> _filterReminders(List<MedicineReminder> reminders) {
    if (_searchQuery.isEmpty) return reminders;
    return reminders.where((r) {
      return r.medicineName.contains(_searchQuery) ||
          r.memberName.contains(_searchQuery);
    }).toList();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('用药提醒'),
        bottom: PreferredSize(
          preferredSize: Size.fromHeight(56.h),
          child: Padding(
            padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 8.h),
            child: TextField(
              controller: _searchController,
              decoration: InputDecoration(
                hintText: '搜索提醒...',
                prefixIcon: const Icon(Icons.search),
                filled: true,
                fillColor: Colors.white,
                border: OutlineInputBorder(
                  borderRadius: BorderRadius.circular(8.r),
                  borderSide: BorderSide.none,
                ),
              ),
              onChanged: (v) => setState(() => _searchQuery = v),
            ),
          ),
        ),
      ),
      body: Consumer<ReminderProvider>(
        builder: (context, provider, child) {
          final filtered = _filterReminders(provider.reminders);
          return ListView.builder(
            itemCount: filtered.length,
            itemBuilder: (context, index) => _buildReminderCard(context, filtered[index], provider),
          );
        },
      ),
    );
  }
}

搜索框放置在AppBar的bottom区域,用户输入时实时过滤提醒列表。搜索范围包括药品名称和服药人姓名,方便用户通过不同方式查找提醒。

提醒分组显示

按启用状态分组显示提醒,让列表更加清晰:

dart 复制代码
Widget _buildGroupedList(List<MedicineReminder> reminders) {
  final activeReminders = reminders.where((r) => r.isActive).toList();
  final inactiveReminders = reminders.where((r) => !r.isActive).toList();

  return ListView(
    padding: EdgeInsets.all(16.w),
    children: [
      if (activeReminders.isNotEmpty) ...[
        _buildSectionHeader('启用中 (${activeReminders.length})'),
        ...activeReminders.map((r) => _buildReminderCard(context, r, provider)),
      ],
      if (inactiveReminders.isNotEmpty) ...[
        SizedBox(height: 16.h),
        _buildSectionHeader('已停用 (${inactiveReminders.length})'),
        ...inactiveReminders.map((r) => _buildReminderCard(context, r, provider)),
      ],
    ],
  );
}

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

提醒按启用状态分为两组,启用中的提醒显示在上方,已停用的显示在下方。每组都有标题显示数量,让用户一目了然。这种分组设计让提醒管理更加有序。

滑动删除功能

为提醒卡片添加滑动删除功能:

dart 复制代码
Widget _buildDismissibleCard(MedicineReminder reminder, ReminderProvider provider) {
  return Dismissible(
    key: Key(reminder.id),
    direction: DismissDirection.endToStart,
    background: Container(
      alignment: Alignment.centerRight,
      padding: EdgeInsets.only(right: 20.w),
      color: Colors.red,
      child: const Icon(Icons.delete, color: Colors.white),
    ),
    confirmDismiss: (direction) async {
      return await showDialog(
        context: context,
        builder: (context) => AlertDialog(
          title: const Text('确认删除'),
          content: Text('确定要删除"${reminder.medicineName}"的提醒吗?'),
          actions: [
            TextButton(
              onPressed: () => Navigator.pop(context, false),
              child: const Text('取消'),
            ),
            TextButton(
              onPressed: () => Navigator.pop(context, true),
              child: const Text('删除', style: TextStyle(color: Colors.red)),
            ),
          ],
        ),
      );
    },
    onDismissed: (direction) {
      provider.deleteReminder(reminder.id);
      Get.snackbar('已删除', '提醒已删除', snackPosition: SnackPosition.BOTTOM);
    },
    child: _buildReminderCard(context, reminder, provider),
  );
}

使用Dismissible组件包裹提醒卡片,支持从右向左滑动删除。删除前弹出确认对话框,防止误操作。删除后显示Snackbar提示,让用户知道操作已完成。

批量操作功能

提供批量启用/停用提醒的功能:

dart 复制代码
bool _isSelectionMode = false;
Set<String> _selectedIds = {};

Widget _buildBatchActions() {
  return Container(
    padding: EdgeInsets.all(16.w),
    decoration: BoxDecoration(
      color: Colors.white,
      boxShadow: [
        BoxShadow(color: Colors.black.withOpacity(0.1), blurRadius: 4, offset: const Offset(0, -2)),
      ],
    ),
    child: Row(
      children: [
        Text('已选择 ${_selectedIds.length} 项', style: TextStyle(fontSize: 14.sp)),
        const Spacer(),
        TextButton(
          onPressed: () => _batchToggle(true),
          child: const Text('全部启用'),
        ),
        TextButton(
          onPressed: () => _batchToggle(false),
          child: const Text('全部停用'),
        ),
        TextButton(
          onPressed: _batchDelete,
          child: const Text('删除', style: TextStyle(color: Colors.red)),
        ),
      ],
    ),
  );
}

void _batchToggle(bool active) {
  final provider = context.read<ReminderProvider>();
  for (final id in _selectedIds) {
    provider.setReminderActive(id, active);
  }
  setState(() {
    _isSelectionMode = false;
    _selectedIds.clear();
  });
}

长按提醒卡片进入选择模式,可以选择多个提醒进行批量操作。底部显示操作栏,提供全部启用、全部停用和删除三个选项。批量操作完成后自动退出选择模式。

提醒统计信息

在列表顶部显示提醒统计信息:

dart 复制代码
Widget _buildStatistics(List<MedicineReminder> reminders) {
  final totalCount = reminders.length;
  final activeCount = reminders.where((r) => r.isActive).length;
  final todayCount = reminders.where((r) {
    final now = DateTime.now();
    return r.reminderTimes.any((t) =>
        t.hour >= now.hour || (t.hour == now.hour && t.minute >= now.minute));
  }).length;

  return Container(
    margin: EdgeInsets.all(16.w),
    padding: EdgeInsets.all(16.w),
    decoration: BoxDecoration(
      color: const Color(0xFF00897B).withOpacity(0.1),
      borderRadius: BorderRadius.circular(12.r),
    ),
    child: Row(
      mainAxisAlignment: MainAxisAlignment.spaceAround,
      children: [
        _buildStatItem('总提醒', totalCount.toString()),
        _buildStatItem('启用中', activeCount.toString()),
        _buildStatItem('今日待服', todayCount.toString()),
      ],
    ),
  );
}

统计信息显示总提醒数、启用中的提醒数和今日待服药的提醒数。这些信息帮助用户快速了解提醒的整体情况。

提醒时间冲突检测

添加新提醒时检测时间冲突:

dart 复制代码
bool _hasTimeConflict(MedicineReminder newReminder) {
  final provider = context.read<ReminderProvider>();
  final existingReminders = provider.reminders.where((r) =>
      r.memberId == newReminder.memberId && r.isActive).toList();

  for (final existing in existingReminders) {
    for (final newTime in newReminder.reminderTimes) {
      for (final existingTime in existing.reminderTimes) {
        final diff = (newTime.hour * 60 + newTime.minute) -
            (existingTime.hour * 60 + existingTime.minute);
        if (diff.abs() < 30) {
          return true;
        }
      }
    }
  }
  return false;
}

检测同一成员的提醒时间是否间隔过近(小于30分钟)。如果存在冲突,提示用户调整时间,避免同时服用多种药物可能带来的风险。

总结

用药提醒列表通过卡片式设计和状态开关,让用户能够方便地管理所有提醒。颜色编码和分层布局提供了清晰的信息展示,Provider管理状态确保数据的响应式更新。搜索、分组、批量操作等功能让提醒管理更加高效便捷。


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

相关推荐
草莓熊Lotso2 分钟前
Linux 文件描述符与重定向实战:从原理到 minishell 实现
android·linux·运维·服务器·数据库·c++·人工智能
恋猫de小郭8 分钟前
Flutter Zero 是什么?它的出现有什么意义?为什么你需要了解下?
android·前端·flutter
一只大侠的侠5 小时前
Flutter开源鸿蒙跨平台训练营 Day 10特惠推荐数据的获取与渲染
flutter·开源·harmonyos
崔庆才丨静觅7 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60617 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了8 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅8 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅8 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
七夜zippoe8 小时前
CANN Runtime任务描述序列化与持久化源码深度解码
大数据·运维·服务器·cann
renke33648 小时前
Flutter for OpenHarmony:色彩捕手——基于HSL色轮与感知色差的交互式色觉训练系统
flutter