在这里插入图片描述

定期驱虫是养猫的必修课。体内驱虫、体外驱虫,时间间隔各不相同,很容易忘记。今天我们来实现一个驱虫记录功能,帮助铲屎官们管理猫咪的驱虫计划。


需求分析

驱虫记录页面需要实现以下功能:

  • 展示驱虫相关的科普知识
  • 列表显示历史驱虫记录
  • 支持快速添加新记录
  • 显示下次驱虫提醒时间

想清楚需求再动手,能避免很多返工。


依赖引入

先把需要的包引入进来:

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:intl/intl.dart';
import '../../providers/health_provider.dart';
import '../../models/health_record.dart';
import 'add_health_record_screen.dart';

Material是Flutter的UI基础库,Provider处理状态管理。
screenutil做屏幕适配,intl负责日期的格式化显示。


页面类定义

驱虫记录页面是一个无状态组件:

class DewormingScreen extends StatelessWidget {
  final String catId;

  const DewormingScreen({super.key, required this.catId});

通过catId参数确定是哪只猫咪的驱虫记录。
StatelessWidget配合Provider使用,数据变化时自动刷新。


主体结构搭建

build方法构建整个页面:

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('驱虫记录')),
      body: Consumer<HealthProvider>(
        builder: (context, provider, child) {
          final records = provider.getRecordsByType(catId, HealthRecordType.deworming);

Scaffold是页面的骨架,包含顶部导航栏和主体内容。
Consumer监听HealthProvider,数据变化时builder会重新执行。


空状态处理

没有记录时显示引导界面:

          if (records.isEmpty) {
            return Center(
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  Icon(Icons.bug_report, size: 80.sp, color: Colors.grey[300]),
                  SizedBox(height: 16.h),
                  Text('暂无驱虫记录', style: TextStyle(color: Colors.grey[600])),

用虫子图标暗示驱虫主题,灰色调表示空状态。
Center和Column组合实现垂直居中布局。

添加引导按钮:

                  SizedBox(height: 16.h),
                  ElevatedButton(
                    onPressed: () => _addRecord(context),
                    child: const Text('添加驱虫记录'),
                  ),
                ],
              ),
            );
          }

空状态下提供明确的操作入口。
用户点击按钮就能直接跳转到添加页面。


列表内容展示

有数据时展示完整列表:

          return ListView(
            padding: EdgeInsets.all(16.w),
            children: [
              _buildDewormingInfo(),
              SizedBox(height: 16.h),
              Text('驱虫记录', style: TextStyle(fontSize: 16.sp, fontWeight: FontWeight.bold)),
              SizedBox(height: 8.h),
              ...records.map((r) => _buildRecordCard(r)),
            ],
          );
        },
      ),

ListView支持滚动,适合展示不定长度的内容。
先放知识卡片,再放记录列表,层次分明。

悬浮添加按钮:

      floatingActionButton: FloatingActionButton(
        onPressed: () => _addRecord(context),
        backgroundColor: Colors.orange,
        child: const Icon(Icons.add),
      ),
    );
  }

FAB是Material Design的标准添加按钮。
固定在右下角,随时可以点击添加新记录。


驱虫知识卡片

科普内容帮助用户了解驱虫知识:

  Widget _buildDewormingInfo() {
    return Card(
      color: Colors.green[50],
      child: Padding(
        padding: EdgeInsets.all(16.w),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Row(
              children: [
                Icon(Icons.info, color: Colors.green, size: 20.sp),
                SizedBox(width: 8.w),
                Text('驱虫知识', style: TextStyle(fontWeight: FontWeight.bold, color: Colors.green[800])),
              ],
            ),

绿色主题与驱虫话题相关联,给人健康的感觉。
Row布局让图标和标题水平排列。

知识内容详情:

            SizedBox(height: 8.h),
            Text(
              '• 体内驱虫:每3个月一次\n'
              '• 体外驱虫:每月一次(夏季)\n'
              '• 常见体内寄生虫:蛔虫、绦虫、钩虫\n'
              '• 常见体外寄生虫:跳蚤、蜱虫、耳螨',
              style: TextStyle(fontSize: 13.sp, color: Colors.green[700]),
            ),
          ],
        ),
      ),
    );
  }

用项目符号列出关键信息,方便快速浏览。
体内体外驱虫的频率不同,这是很多新手容易忽略的。


记录卡片设计

每条驱虫记录用卡片展示:

  Widget _buildRecordCard(HealthRecord record) {
    return Card(
      margin: EdgeInsets.only(bottom: 8.h),
      child: ListTile(
        leading: CircleAvatar(
          backgroundColor: Colors.green[100],
          child: Icon(Icons.bug_report, color: Colors.green, size: 20.sp),
        ),
        title: Text(record.title),

Card提供阴影和圆角效果,视觉上更有层次。
CircleAvatar作为左侧图标,统一的绿色主题。

日期信息显示:

        subtitle: Text(DateFormat('yyyy-MM-dd').format(record.date)),

副标题显示驱虫日期,格式清晰易读。
DateFormat来自intl包,支持各种日期格式。

下次驱虫提醒:

        trailing: record.nextDate != null
            ? Column(
                mainAxisAlignment: MainAxisAlignment.center,
                crossAxisAlignment: CrossAxisAlignment.end,
                children: [
                  Text('下次', style: TextStyle(fontSize: 10.sp, color: Colors.grey)),
                  Text(DateFormat('MM-dd').format(record.nextDate!), style: TextStyle(color: Colors.orange)),
                ],
              )
            : null,
      ),
    );
  }

下次驱虫日期用橙色突出显示,起到提醒作用。
条件渲染,没有设置下次日期时不显示trailing。


添加记录导航

跳转到添加页面的方法:

  void _addRecord(BuildContext context) {
    Navigator.push(context, MaterialPageRoute(
      builder: (_) => AddHealthRecordScreen(catId: catId, initialType: HealthRecordType.deworming),
    ));
  }
}

Navigator.push实现页面跳转。
initialType设为deworming,添加页面会默认选中驱虫类型。


数据筛选逻辑

Provider中的筛选方法:

List<HealthRecord> getRecordsByType(String catId, HealthRecordType type) {
  return _records
      .where((r) => r.catId == catId && r.type == type)
      .toList()
    ..sort((a, b) => b.date.compareTo(a.date));
}

where方法筛选出符合条件的记录。
级联操作符…让排序和转换一气呵成。


颜色方案设计

驱虫页面使用绿色系:

Colors.green[50]   // 卡片背景色
Colors.green[100]  // 头像背景色
Colors.green[700]  // 正文颜色
Colors.green[800]  // 标题颜色

绿色给人健康、自然的感觉,与驱虫主题契合。
不同深浅的绿色形成视觉层次。

强调色使用橙色:

Colors.orange  // 下次日期、FAB按钮

橙色作为App的主题色,用于强调重要信息。
与绿色形成对比,更容易吸引注意力。


布局技巧总结

Column实现垂直布局:

Column(
  crossAxisAlignment: CrossAxisAlignment.start,
  children: [
    Row(...),
    SizedBox(height: 8.h),
    Text(...),
  ],
)

crossAxisAlignment设为start让内容左对齐。
SizedBox作为间距组件,比Padding更灵活。

Row实现水平布局:

Row(
  children: [
    Icon(Icons.info, color: Colors.green, size: 20.sp),
    SizedBox(width: 8.w),
    Text('驱虫知识', ...),
  ],
)

Row中的子组件水平排列。
SizedBox控制组件之间的间距。


ListTile详解

ListTile是列表项的标准组件:

ListTile(
  leading: CircleAvatar(...),  // 左侧图标
  title: Text(record.title),   // 主标题
  subtitle: Text(...),         // 副标题
  trailing: Column(...),       // 右侧内容
)

四个位置可以放置不同内容,布局自动处理。
比手动用Row和Column布局省事很多。


条件渲染写法

三元表达式处理可选内容:

trailing: record.nextDate != null
    ? Column(...)
    : null,

当nextDate存在时显示Column,否则为null。
null的trailing不会占用空间。

字符串拼接中的条件:

'${record.hospital != null ? ' · ${record.hospital}' : ''}'

如果hospital不为null,就拼接上去。
这种写法在显示可选信息时很常用。


日期格式化

完整日期格式:

DateFormat('yyyy-MM-dd').format(record.date)

输出类似"2024-03-15"的格式。
yyyy是四位年份,MM是两位月份,dd是两位日期。

简短日期格式:

DateFormat('MM-dd').format(record.nextDate!)

只显示月日,适合空间有限的场景。
感叹号表示已确认nextDate不为null。


屏幕适配实践

使用screenutil的适配单位:

Icon(Icons.bug_report, size: 80.sp, color: Colors.grey[300])
SizedBox(height: 16.h)
EdgeInsets.all(16.w)
TextStyle(fontSize: 13.sp, color: Colors.green[700])

.sp用于字体和图标,根据屏幕宽度缩放。
.h用于高度,.w用于宽度,保证不同设备上的一致性。


Consumer使用要点

Consumer监听Provider变化:

Consumer<HealthProvider>(
  builder: (context, provider, child) {
    final records = provider.getRecordsByType(...);
    // 使用records构建UI
  },
)

builder函数在Provider数据变化时重新执行。
provider参数可以直接调用Provider的方法。


空状态设计原则

好的空状态应该包含:

Center(
  child: Column(
    mainAxisAlignment: MainAxisAlignment.center,
    children: [
      Icon(...),           // 主题相关的图标
      SizedBox(...),
      Text('暂无驱虫记录'),  // 说明文字
      SizedBox(...),
      ElevatedButton(...), // 操作引导
    ],
  ),
)

图标让页面不会太空,文字说明当前状态。
按钮引导用户进行下一步操作。


小结

驱虫记录页面的实现涵盖了这些知识点:

  • Provider和Consumer的配合使用
  • 空状态和列表状态的切换
  • Card和ListTile的组合
  • 条件渲染和日期格式化

这些都是Flutter开发中的常用技巧,掌握了就能应对大部分列表页面的开发。


欢迎加入OpenHarmony跨平台开发社区,一起交流Flutter开发经验:

https://openharmonycrossplatform.csdn.net

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐