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

相关推荐
利刃大大1 小时前
【Vue】scoped作用 && 父子组件通信 && props && emit
前端·javascript·vue.js
小白郭莫搞科技2 小时前
鸿蒙跨端框架Flutter学习:ListView卡片样式详解
linux·服务器·windows
-凌凌漆-2 小时前
【Vue】Vue3 vite build 之后空白
前端·javascript·vue.js
心柠2 小时前
前端工程化
前端
沐雪架构师2 小时前
核心组件2
前端
qq_336313932 小时前
javaweb-Vue3
前端·javascript·vue.js
kirk_wang2 小时前
Flutter艺术探索-Flutter内存管理:内存泄漏检测与优化
flutter·移动开发·flutter教程·移动开发教程
Miguo94well2 小时前
Flutter框架跨平台鸿蒙开发——演讲稿生成器APP的开发流程
flutter·华为·harmonyos·鸿蒙
雨季6662 小时前
Flutter 三端应用实战:OpenHarmony 简易数字累加器开发指南
开发语言·flutter·ui·ecmascript