Flutter for OpenHarmony 猫咪管家App实战 - 喂食列表实现
本文介绍了如何实现一个猫咪喂食记录列表页面,主要功能包括: 按日期分组展示喂食记录,区分当天显示为"今天" 支持滑动删除记录,带删除动画效果 空状态友好提示,引导用户添加记录 底部悬浮按钮快速跳转添加页面 使用Provider管理数据,实现数据变化自动刷新 技术要点: 通过Map结构对记录按日期分组 Dismissible组件实现滑动删除 Consumer监听数据变化 国际化处

记录猫咪每天吃了什么,是科学养猫的基础。今天我们来实现喂食记录列表页面,按日期分组展示,支持滑动删除,让喂食记录一目了然。
功能需求
喂食列表页面需要实现:
- 按日期分组展示记录
- 显示食物类型、名称、数量
- 支持滑动删除
- 空状态友好提示
- 快速添加新记录
这种按日期分组的列表在很多App中都很常见。
依赖引入
首先导入需要的包:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:intl/intl.dart';
import '../../providers/cat_provider.dart';
import '../../models/feeding_record.dart';
import 'add_feeding_screen.dart';
Provider管理喂食数据,数据变化时自动刷新。
intl用于日期格式化和中文星期显示。
无状态组件
喂食列表不需要维护额外状态:
class FeedingListScreen extends StatelessWidget {
final String catId;
const FeedingListScreen({super.key, required this.catId});
catId标识是哪只猫咪的喂食记录。
数据由Provider管理,页面本身不需要状态。
页面结构
build方法构建整体布局:
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('喂食记录')),
body: Consumer<CatProvider>(
builder: (context, provider, child) {
final records = provider.getFeedingRecordsForCat(catId);
Consumer监听CatProvider的变化。
获取指定猫咪的喂食记录。
空状态处理
没有记录时显示引导界面:
if (records.isEmpty) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.restaurant, size: 80.sp, color: Colors.grey[300]),
SizedBox(height: 16.h),
Text('暂无喂食记录', style: TextStyle(color: Colors.grey[600])),
],
),
);
}
餐具图标暗示喂食主题。
灰色调表示空状态。
按日期分组
将记录按日期分组:
final groupedRecords = <String, List<FeedingRecord>>{};
for (var record in records) {
final dateKey = DateFormat('yyyy-MM-dd').format(record.dateTime);
groupedRecords.putIfAbsent(dateKey, () => []).add(record);
}
用Map存储分组结果,key是日期字符串。
putIfAbsent确保每个日期只创建一次列表。
分组列表展示
用ListView展示分组后的数据:
return ListView.builder(
padding: EdgeInsets.all(16.w),
itemCount: groupedRecords.length,
itemBuilder: (context, index) {
final dateKey = groupedRecords.keys.elementAt(index);
final dayRecords = groupedRecords[dateKey]!;
return _buildDaySection(context, dateKey, dayRecords, provider);
},
);
},
),
itemCount是分组数量,不是记录数量。
每个分组调用_buildDaySection构建。
悬浮添加按钮
页面底部的FAB:
floatingActionButton: FloatingActionButton(
onPressed: () => Navigator.push(context, MaterialPageRoute(
builder: (_) => AddFeedingScreen(catId: catId),
)),
backgroundColor: Colors.orange,
child: const Icon(Icons.add),
),
);
}
点击跳转到添加喂食页面。
传入catId标识是哪只猫咪。
日期分组组件
构建一天的记录分组:
Widget _buildDaySection(BuildContext context, String dateKey, List<FeedingRecord> records, CatProvider provider) {
final date = DateTime.parse(dateKey);
final isToday = DateFormat('yyyy-MM-dd').format(DateTime.now()) == dateKey;
解析日期字符串为DateTime对象。
判断是否是今天,用于特殊显示。
日期标题:
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: EdgeInsets.symmetric(vertical: 8.h),
child: Row(
children: [
Text(
isToday ? '今天' : DateFormat('MM月dd日').format(date),
style: TextStyle(fontSize: 14.sp, fontWeight: FontWeight.bold, color: Colors.grey[700]),
),
SizedBox(width: 8.w),
Text(
DateFormat('EEEE', 'zh_CN').format(date),
style: TextStyle(fontSize: 12.sp, color: Colors.grey[500]),
),
],
),
),
今天显示"今天",其他日期显示月日。
'zh_CN’让星期显示为中文。
当天记录卡片
Card包裹当天的所有记录:
Card(
child: ListView.separated(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: records.length,
separatorBuilder: (_, __) => const Divider(height: 1),
itemBuilder: (context, index) {
final record = records[index];
return Dismissible(
shrinkWrap让ListView高度自适应内容。
NeverScrollableScrollPhysics禁止内部滚动。
滑动删除
Dismissible实现滑动删除:
return Dismissible(
key: Key(record.id),
direction: DismissDirection.endToStart,
background: Container(
color: Colors.red,
alignment: Alignment.centerRight,
padding: EdgeInsets.only(right: 16.w),
child: const Icon(Icons.delete, color: Colors.white),
),
onDismissed: (_) => provider.deleteFeedingRecord(record.id),
从右向左滑动显示删除背景。
onDismissed调用Provider删除记录。
记录项内容
ListTile展示记录详情:
child: ListTile(
leading: CircleAvatar(
backgroundColor: _getFoodTypeColor(record.foodType).withOpacity(0.1),
child: Icon(_getFoodTypeIcon(record.foodType), color: _getFoodTypeColor(record.foodType), size: 20.sp),
),
title: Text(record.foodName),
subtitle: Text('${record.amount}${record.unit} · ${record.foodTypeString}'),
trailing: Text(
DateFormat('HH:mm').format(record.dateTime),
style: TextStyle(color: Colors.grey[600]),
),
),
);
},
),
),
SizedBox(height: 8.h),
],
);
}
leading显示食物类型图标。
trailing显示喂食时间。
食物类型颜色
不同类型用不同颜色:
Color _getFoodTypeColor(FoodType type) {
switch (type) {
case FoodType.dryFood: return Colors.brown;
case FoodType.wetFood: return Colors.orange;
case FoodType.snack: return Colors.pink;
case FoodType.water: return Colors.blue;
case FoodType.other: return Colors.grey;
}
}
干粮用棕色,湿粮用橙色,饮水用蓝色。
颜色区分让用户快速识别类型。
食物类型图标
不同类型用不同图标:
IconData _getFoodTypeIcon(FoodType type) {
switch (type) {
case FoodType.dryFood: return Icons.grain;
case FoodType.wetFood: return Icons.soup_kitchen;
case FoodType.snack: return Icons.cookie;
case FoodType.water: return Icons.water_drop;
case FoodType.other: return Icons.restaurant;
}
}
}
图标让类型更直观。
grain表示干粮,soup_kitchen表示湿粮。
分组逻辑详解
Map的putIfAbsent方法:
groupedRecords.putIfAbsent(dateKey, () => []).add(record);
如果key不存在,创建空列表。
然后将记录添加到列表中。
这行代码等价于:
if (!groupedRecords.containsKey(dateKey)) {
groupedRecords[dateKey] = [];
}
groupedRecords[dateKey]!.add(record);
putIfAbsent更简洁。
一行代码完成判断和添加。
嵌套ListView
ListView嵌套的处理:
ListView.separated(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
...
)
shrinkWrap让内部ListView高度自适应。
NeverScrollableScrollPhysics禁止内部滚动,由外部ListView统一滚动。
Divider分隔线
列表项之间的分隔:
separatorBuilder: (_, __) => const Divider(height: 1),
ListView.separated自带分隔线构建器。
height: 1让分隔线很细。
日期格式化
中文星期显示:
DateFormat('EEEE', 'zh_CN').format(date)
EEEE表示完整的星期名称。
'zh_CN’让输出变成中文。
时间格式:
DateFormat('HH:mm').format(record.dateTime)
只显示小时和分钟。
24小时制。
今天判断
判断是否是今天:
final isToday = DateFormat('yyyy-MM-dd').format(DateTime.now()) == dateKey;
将今天的日期格式化后与dateKey比较。
相等则是今天。
Card嵌套布局
Card包裹当天的记录:
Card(
child: ListView.separated(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: records.length,
...
),
)
Card提供阴影和圆角效果。
内部的ListView展示当天的所有记录。
小结
喂食列表页面涉及的知识点:
- 按日期分组的实现
- 嵌套ListView的处理
- Dismissible滑动删除
- 中文日期格式化
这种分组列表的模式在很多场景都能用到。
欢迎加入OpenHarmony跨平台开发社区,一起交流Flutter开发经验:
https://openharmonycrossplatform.csdn.net
更多推荐

所有评论(0)