flutter_for_openharmony家庭药箱管理app实战+药品分类实现

药品分类功能帮助用户按照药品类型快速查找和管理药品。通过分类视图,用户可以一目了然地看到每个分类下有多少药品,点击分类可以查看该分类的所有药品。

分类功能设计

分类页面包含两个层级:分类网格视图分类详情列表。网格视图展示所有分类及药品数量,详情列表展示某个分类下的所有药品。使用图标和颜色区分不同分类,提升视觉识别度。

分类图标映射

为每个分类定义对应的图标:

dart 复制代码
final categoryIcons = {
  '感冒用药': Icons.sick,
  '解热镇痛': Icons.thermostat,
  '抗生素': Icons.medication_liquid,
  '消化系统': Icons.restaurant,
  '心血管': Icons.favorite,
  '维生素': Icons.local_pharmacy,
  '外用药': Icons.healing,
  '中成药': Icons.spa,
  '儿童用药': Icons.child_care,
  '其他': Icons.more_horiz,
};

使用Map将分类名称映射到对应的图标。每个分类都有独特的图标,让用户能够快速识别。如果分类不在映射表中,使用默认的药品图标。

分类网格视图

使用GridView展示所有分类:

dart 复制代码
@override
Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(
      title: const Text('药品分类'),
    ),
    body: Consumer<MedicineProvider>(
      builder: (context, provider, child) {
        final categories = provider.categories;
        
        if (categories.isEmpty) {
          return Center(
            child: Text('暂无药品分类', style: TextStyle(color: Colors.grey[500])),
          );
        }

        return GridView.builder(
          padding: EdgeInsets.all(16.w),
          gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
            crossAxisCount: 2,
            crossAxisSpacing: 12.w,
            mainAxisSpacing: 12.h,
            childAspectRatio: 1.5,
          ),
          itemCount: categories.length,
          itemBuilder: (context, index) {
            final category = categories[index];
            final count = provider.getMedicinesByCategory(category).length;
            final icon = categoryIcons[category] ?? Icons.medication;

            return _buildCategoryCard(category, count, icon);
          },
        );
      },
    ),
  );
}

使用Consumer监听Provider,当药品数据变化时自动更新分类列表。GridView.builder创建网格布局,crossAxisCount: 2表示每行显示2个分类。childAspectRatio: 1.5设置卡片的宽高比,让卡片呈现横向矩形。

分类卡片设计

每个分类卡片显示图标、名称和数量:

dart 复制代码
Widget _buildCategoryCard(String category, int count, IconData icon) {
  return GestureDetector(
    onTap: () => Get.to(() => CategoryDetailScreen(category: category)),
    child: Container(
      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(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Icon(icon, size: 32.sp, color: const Color(0xFF00897B)),
          SizedBox(height: 8.h),
          Text(
            category,
            style: TextStyle(fontSize: 14.sp, fontWeight: FontWeight.w500),
          ),
          Text(
            '$count 种',
            style: TextStyle(fontSize: 12.sp, color: Colors.grey[500]),
          ),
        ],
      ),
    ),
  );
}

卡片采用垂直布局,从上到下依次是图标、分类名称、药品数量。图标使用主题色,大小为32.sp。分类名称使用中等粗细字体,数量使用灰色小字体。点击卡片跳转到分类详情页面。

分类详情页面

分类详情页面展示该分类下的所有药品:

dart 复制代码
class CategoryDetailScreen extends StatelessWidget {
  final String category;

  const CategoryDetailScreen({super.key, required this.category});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(category),
      ),
      body: Consumer<MedicineProvider>(
        builder: (context, provider, child) {
          final medicines = provider.getMedicinesByCategory(category);

          if (medicines.isEmpty) {
            return Center(
              child: Text('该分类暂无药品', style: TextStyle(color: Colors.grey[500])),
            );
          }

          return ListView.builder(
            padding: EdgeInsets.all(16.w),
            itemCount: medicines.length,
            itemBuilder: (context, index) {
              final medicine = medicines[index];
              return _buildMedicineItem(medicine);
            },
          );
        },
      ),
    );
  }
}

AppBar标题显示分类名称。使用Consumer监听Provider,调用getMedicinesByCategory方法获取该分类的药品列表。如果列表为空,显示友好的空状态提示。

药品列表项

详情页面的药品列表项使用ListTile:

dart 复制代码
Widget _buildMedicineItem(Medicine medicine) {
  return ListTile(
    leading: Container(
      width: 48.w,
      height: 48.w,
      decoration: BoxDecoration(
        color: const Color(0xFF00897B).withOpacity(0.1),
        borderRadius: BorderRadius.circular(8.r),
      ),
      child: Icon(Icons.medication, color: const Color(0xFF00897B)),
    ),
    title: Text(medicine.name),
    subtitle: Text('库存: ${medicine.quantity}${medicine.unit}'),
    trailing: const Icon(Icons.chevron_right),
    onTap: () => Get.to(() => MedicineDetailScreen(medicine: medicine)),
  );
}

ListTile左侧是药品图标,标题显示药品名称,副标题显示库存信息,右侧是箭头图标。点击后跳转到药品详情页面。

Provider分类逻辑

Provider中实现分类相关方法:

dart 复制代码
List<String> get categories =>
    _medicines.map((m) => m.category).toSet().toList();

List<Medicine> getMedicinesByCategory(String category) {
  return _medicines.where((m) => m.category == category).toList();
}

categories属性通过map提取所有药品的分类,使用toSet()去重,再转换为列表。getMedicinesByCategory方法筛选指定分类的药品。这两个方法配合使用,实现了完整的分类功能。

网格布局优势

使用GridView展示分类有几个优势:可以在一屏内展示更多分类,减少滚动次数。网格布局视觉上更加紧凑,空间利用率高。每个分类卡片大小一致,看起来整齐美观。

空状态处理

分类页面和详情页面都处理了空状态。分类页面为空时提示"暂无药品分类",详情页面为空时提示"该分类暂无药品"。这种友好的提示让用户明确当前状态,不会感到困惑。

响应式更新

使用Consumer监听Provider,当药品数据变化时,分类列表和详情列表都会自动更新。比如添加新药品后,对应分类的数量会立即增加,新分类会自动出现在网格中。

分类颜色映射

为每个分类定义对应的颜色:

dart 复制代码
final categoryColors = {
  '感冒用药': Colors.blue,
  '解热镇痛': Colors.red,
  '抗生素': Colors.purple,
  '消化系统': Colors.orange,
  '心血管': Colors.pink,
  '维生素': Colors.green,
  '外用药': Colors.teal,
  '中成药': Colors.brown,
  '儿童用药': Colors.cyan,
  '其他': Colors.grey,
};

Color _getCategoryColor(String category) {
  return categoryColors[category] ?? const Color(0xFF00897B);
}

每个分类使用独特的颜色,让用户能够通过颜色快速识别分类。颜色选择考虑了语义关联,比如心血管使用粉红色(与心脏相关),消化系统使用橙色(与食物相关)。如果分类不在映射表中,使用默认的主题色。

分类卡片动画

为分类卡片添加点击动画效果:

dart 复制代码
Widget _buildCategoryCard(String category, int count, IconData icon, Color color) {
  return TweenAnimationBuilder<double>(
    tween: Tween(begin: 1.0, end: 1.0),
    duration: const Duration(milliseconds: 150),
    builder: (context, scale, child) {
      return Transform.scale(
        scale: scale,
        child: GestureDetector(
          onTapDown: (_) => setState(() => _pressedCategory = category),
          onTapUp: (_) {
            setState(() => _pressedCategory = null);
            Get.to(() => CategoryDetailScreen(category: category));
          },
          onTapCancel: () => setState(() => _pressedCategory = null),
          child: AnimatedContainer(
            duration: const Duration(milliseconds: 150),
            transform: Matrix4.identity()..scale(_pressedCategory == category ? 0.95 : 1.0),
            decoration: BoxDecoration(
              color: Colors.white,
              borderRadius: BorderRadius.circular(12.r),
              boxShadow: [
                BoxShadow(
                  color: color.withOpacity(_pressedCategory == category ? 0.2 : 0.1),
                  blurRadius: _pressedCategory == category ? 15 : 10,
                  offset: const Offset(0, 2),
                ),
              ],
            ),
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                Container(
                  padding: EdgeInsets.all(12.w),
                  decoration: BoxDecoration(
                    color: color.withOpacity(0.1),
                    shape: BoxShape.circle,
                  ),
                  child: Icon(icon, size: 28.sp, color: color),
                ),
                SizedBox(height: 8.h),
                Text(category, style: TextStyle(fontSize: 14.sp, fontWeight: FontWeight.w500)),
                Text('$count 种', style: TextStyle(fontSize: 12.sp, color: Colors.grey[500])),
              ],
            ),
          ),
        ),
      );
    },
  );
}

点击卡片时会有轻微的缩放效果,阴影也会相应变化。这种微交互让用户感受到操作的反馈,提升了用户体验。使用AnimatedContainer实现平滑的动画过渡。

分类详情页面头部

详情页面添加分类信息头部:

dart 复制代码
Widget _buildCategoryHeader(String category, int count, Color color) {
  return Container(
    padding: EdgeInsets.all(20.w),
    decoration: BoxDecoration(
      gradient: LinearGradient(
        colors: [color, color.withOpacity(0.7)],
        begin: Alignment.topLeft,
        end: Alignment.bottomRight,
      ),
    ),
    child: Row(
      children: [
        Container(
          padding: EdgeInsets.all(16.w),
          decoration: BoxDecoration(
            color: Colors.white.withOpacity(0.2),
            borderRadius: BorderRadius.circular(16.r),
          ),
          child: Icon(
            categoryIcons[category] ?? Icons.medication,
            size: 32.sp,
            color: Colors.white,
          ),
        ),
        SizedBox(width: 16.w),
        Expanded(
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              Text(
                category,
                style: TextStyle(
                  fontSize: 24.sp,
                  fontWeight: FontWeight.bold,
                  color: Colors.white,
                ),
              ),
              SizedBox(height: 4.h),
              Text(
                '共 $count 种药品',
                style: TextStyle(fontSize: 14.sp, color: Colors.white70),
              ),
            ],
          ),
        ),
      ],
    ),
  );
}

详情页面头部使用分类对应的颜色渐变背景,显示分类图标、名称和药品数量。这种设计让用户明确知道当前查看的是哪个分类,同时保持了视觉的一致性。

搜索过滤功能

在分类详情页面添加搜索功能:

dart 复制代码
Widget _buildSearchBar() {
  return Container(
    margin: EdgeInsets.all(16.w),
    padding: EdgeInsets.symmetric(horizontal: 16.w),
    decoration: BoxDecoration(
      color: Colors.grey[100],
      borderRadius: BorderRadius.circular(24.r),
    ),
    child: TextField(
      controller: _searchController,
      decoration: InputDecoration(
        hintText: '在此分类中搜索',
        hintStyle: TextStyle(color: Colors.grey[400]),
        border: InputBorder.none,
        icon: Icon(Icons.search, color: Colors.grey[400]),
      ),
      onChanged: (value) {
        setState(() {
          _filteredMedicines = _medicines
              .where((m) => m.name.toLowerCase().contains(value.toLowerCase()))
              .toList();
        });
      },
    ),
  );
}

搜索框使用圆角矩形设计,与整体风格保持一致。输入时实时过滤药品列表,使用不区分大小写的包含匹配。这让用户能够在分类内快速找到目标药品。

排序功能

支持按不同条件排序药品:

dart 复制代码
enum SortType { name, quantity, expiryDate }

void _sortMedicines(SortType type) {
  setState(() {
    switch (type) {
      case SortType.name:
        _filteredMedicines.sort((a, b) => a.name.compareTo(b.name));
        break;
      case SortType.quantity:
        _filteredMedicines.sort((a, b) => a.quantity.compareTo(b.quantity));
        break;
      case SortType.expiryDate:
        _filteredMedicines.sort((a, b) => a.expiryDate.compareTo(b.expiryDate));
        break;
    }
    _currentSort = type;
  });
}

用户可以按名称、库存数量或有效期排序药品。排序状态保存在_currentSort变量中,UI会显示当前的排序方式。这种功能让用户能够根据需要快速找到目标药品。

性能优化

分类功能的性能优化包括:

  1. 懒加载:GridView.builder只构建可见的卡片,减少内存占用
  2. 缓存计算:分类列表和数量在Provider中计算,避免重复计算
  3. 条件渲染:空状态和有数据状态使用条件渲染,减少不必要的Widget
  4. 图片缓存:如果使用分类图片,应该使用CachedNetworkImage缓存

用户体验设计

分类功能的用户体验设计包括:

  1. 视觉识别:每个分类使用独特的图标和颜色,便于快速识别
  2. 数量提示:卡片上显示药品数量,让用户了解分类规模
  3. 空状态处理:分类为空时显示友好提示,引导用户添加药品
  4. 搜索过滤:在分类内支持搜索,提高查找效率
  5. 排序功能:支持多种排序方式,满足不同需求

总结

药品分类功能通过网格视图和详情列表,为用户提供了直观的分类浏览体验。图标映射增强了视觉识别度,响应式更新确保数据的实时性。Provider管理分类逻辑,保持代码的清晰和可维护性。


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

相关推荐
2401_892000522 小时前
Flutter for OpenHarmony 猫咪管家App实战 - 添加支出实现
前端·javascript·flutter
梁下轻语的秋缘2 小时前
Prompt工程核心指南:从入门到精通,让AI精准响应你的需求
大数据·人工智能·prompt
ʚB҉L҉A҉C҉K҉.҉基҉德҉^҉大2 小时前
自动化机器学习(AutoML)库TPOT使用指南
jvm·数据库·python
福客AI智能客服3 小时前
工单智转:电商智能客服与客服AI系统重构售后服务效率
大数据·人工智能
哈__3 小时前
多模融合 一体替代:金仓数据库 KingbaseES 重构企业级统一数据基座
数据库·重构
老邓计算机毕设3 小时前
SSM医院病人信息管理系统e7f6b(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面
数据库·医院信息化·ssm 框架·病人信息管理
AIGC合规助手3 小时前
AI智能硬件I万亿市场预测+算法、大模型备案合规手册
大数据·人工智能·智能硬件
科技宅说4 小时前
聚力报告文学跨界融合 践行国际传播与地域深耕
大数据
dyyx1114 小时前
使用Scikit-learn进行机器学习模型评估
jvm·数据库·python