Flutter for OpenHarmony 美食烹饪助手 App 实战:收藏菜谱功能实现
本文介绍了Flutter收藏菜谱功能的实现方案。通过网格布局展示用户收藏的菜谱卡片,每个卡片包含图片、名称和收藏标记。采用两列布局平衡展示效果与可用空间,使用红色心形图标突出收藏状态。实现细节包括: 使用StatelessWidget构建收藏页面 GridView.builder实现自适应网格布局 卡片设计包含图片区域、收藏图标和文字信息 添加点击跳转和取消收藏功能 处理空状态显示 该方案兼顾视觉

在浏览菜谱时,我们总会遇到一些特别喜欢的,想要保存下来以后再做。今天我们要实现收藏菜谱功能,让用户能够收藏喜欢的菜谱,并方便地查看所有收藏。
收藏功能的设计思路
收藏功能要解决两个问题:如何展示收藏的菜谱,以及如何让用户快速找到想要的菜谱。我选择了网格布局,因为网格能在一屏内展示更多内容,用户可以快速浏览。
每个菜谱卡片显示图片、名称和收藏标记。图片是最重要的,因为人们对图片的记忆比文字更深刻。收藏标记用红色的心形图标,表示这是用户喜欢的菜谱。
网格采用两列布局,这个数字在手机屏幕上刚好合适。一列会显得太空,三列又会让每个卡片太小。两列是一个平衡点,既能展示足够的内容,每个卡片又有足够的空间。
创建无状态组件
收藏页面的内容相对固定,使用 StatelessWidget 就够了。
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
class FavoriteRecipesPage extends StatelessWidget {
const FavoriteRecipesPage({super.key});
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('我的收藏')),
body: GridView.builder(
padding: EdgeInsets.all(16.w),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
crossAxisSpacing: 12.w,
mainAxisSpacing: 12.h,
childAspectRatio: 0.8,
),
itemCount: 20,
itemBuilder: (context, index) => _buildCard(index),
),
);
}
页面结构很简单,就是一个 AppBar 和一个 GridView。AppBar 只有标题,保持简洁。
GridView.builder 的 padding 设置为 16.w,让网格内容不要紧贴屏幕边缘。gridDelegate 定义了网格的布局规则。
crossAxisCount 设置为 2,表示两列。crossAxisSpacing 和 mainAxisSpacing 都设置为 12,让卡片之间有适当的间距。
childAspectRatio 设置为 0.8,表示宽高比是 0.8:1,也就是卡片比正方形稍微高一些。这样可以容纳图片和文字信息。
itemCount 现在是固定的 20,实际开发中应该根据收藏数量动态设置。
实现菜谱卡片
每个菜谱卡片包含图片、名称和收藏标记。
Widget _buildCard(int index) {
return Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12.r),
),
卡片使用白色背景和圆角。这里没有设置阴影,因为背景色已经是浅灰色了,白色卡片本身就有足够的对比度。
child: Column(
children: [
Stack(
children: [
Container(
height: 120.h,
decoration: BoxDecoration(
color: Colors.orange.shade100,
borderRadius: BorderRadius.vertical(top: Radius.circular(12.r)),
),
child: Center(child: Icon(Icons.restaurant, size: 50.sp, color: Colors.orange)),
),
顶部是菜谱图片。使用 Stack 是因为我们要在图片上叠加收藏标记。图片容器高度 120.h,背景色是橙色的浅色版本。
borderRadius 只设置顶部的圆角,让图片和卡片的圆角对齐。实际开发中应该显示真实的图片,这里用图标代替。
Positioned(
top: 8.h,
right: 8.w,
child: Icon(Icons.favorite, color: Colors.red, size: 20.sp),
),
],
),
收藏标记使用 Positioned 定位在右上角。图标是红色的心形,大小 20.sp。红色表示这是用户喜欢的菜谱,心形是收藏的通用图标。
top 和 right 都设置为 8,让图标不要紧贴边缘。这个位置既醒目,又不会遮挡图片的主要内容。
Padding(
padding: EdgeInsets.all(8.w),
child: Text('收藏菜谱 ${index + 1}', style: TextStyle(fontSize: 14.sp, fontWeight: FontWeight.bold)),
),
],
),
);
}
}
底部是菜谱名称。padding 设置为 8.w,让文字不要紧贴边缘。文字使用粗体,字号 14.sp。
这里没有显示其他信息,比如时间、难度等,是为了保持卡片简洁。如果信息太多,卡片会显得拥挤。用户主要是通过图片来识别菜谱的。
添加点击交互
点击卡片应该跳转到菜谱详情页:
Widget _buildCard(int index) {
return GestureDetector(
onTap: () {
Get.to(() => RecipeDetailPage(recipeId: 'recipe_$index'));
},
child: Container(
// ...
),
);
}
使用 GestureDetector 包裹整个卡片,点击时跳转到详情页。传递菜谱 ID 作为参数,详情页会根据这个 ID 加载对应的菜谱数据。
在详情页中,用户可以查看完整的菜谱信息,也可以取消收藏。取消收藏后,这个卡片应该从列表中移除。
实现取消收藏
用户可能想取消收藏某些菜谱。可以在卡片上添加一个取消收藏的按钮:
Positioned(
top: 8.h,
right: 8.w,
child: GestureDetector(
onTap: () {
_confirmUnfavorite(context, index);
},
child: Container(
padding: EdgeInsets.all(4.w),
decoration: BoxDecoration(
color: Colors.white,
shape: BoxShape.circle,
),
child: Icon(Icons.favorite, color: Colors.red, size: 20.sp),
),
),
)
把收藏图标改成可点击的按钮。点击时弹出确认对话框,避免误操作。图标外面包了一个白色的圆形容器,让它更醒目,也更容易点击。
void _confirmUnfavorite(BuildContext context, int index) {
showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: Text('取消收藏'),
content: Text('确定要取消收藏这道菜谱吗?'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: Text('取消'),
),
TextButton(
onPressed: () {
Navigator.pop(context);
// 执行取消收藏操作
Get.snackbar('成功', '已取消收藏');
},
child: Text('确定'),
),
],
);
},
);
}
确认对话框和删除确认类似,但语气更温和。取消收藏不是危险操作,所以按钮不用红色。
添加空状态
如果用户还没有收藏任何菜谱,需要显示空状态:
if (favorites.isEmpty) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.favorite_border, size: 64.sp, color: Colors.grey),
SizedBox(height: 16.h),
Text('还没有收藏菜谱', style: TextStyle(color: Colors.grey)),
SizedBox(height: 8.h),
ElevatedButton(
onPressed: () => Get.back(),
child: Text('去发现菜谱'),
),
],
),
);
}
空状态使用空心的心形图标,表示还没有收藏。文字说明当前状态,按钮引导用户去浏览菜谱。
按钮点击后返回上一页,用户可以继续浏览菜谱并收藏。这种引导能帮助用户快速上手。
添加搜索功能
如果收藏的菜谱很多,可以添加搜索功能:
AppBar(
title: Text('我的收藏'),
actions: [
IconButton(
icon: Icon(Icons.search),
onPressed: () {
showSearch(
context: context,
delegate: FavoriteSearchDelegate(),
);
},
),
],
)
搜索按钮放在 AppBar 右侧,点击后打开搜索页面。用户可以输入关键词,快速找到想要的菜谱。
添加分类筛选
收藏的菜谱可能来自不同的分类,可以添加筛选功能:
Container(
padding: EdgeInsets.all(16.w),
child: Wrap(
spacing: 8.w,
children: ['全部', '川菜', '粤菜', '西餐'].map((category) {
return ChoiceChip(
label: Text(category),
selected: selectedCategory == category,
onSelected: (selected) {
setState(() {
selectedCategory = category;
});
},
);
}).toList(),
),
)
筛选标签放在网格上方,用户可以按分类查看收藏。这需要把 StatelessWidget 改成 StatefulWidget,用一个变量来存储选中的分类。
添加排序功能
用户可能想按不同的方式排序收藏,比如按收藏时间、按名称等:
AppBar(
title: Text('我的收藏'),
actions: [
PopupMenuButton<String>(
onSelected: (value) {
// 根据选择的方式排序
},
itemBuilder: (context) => [
PopupMenuItem(value: 'time', child: Text('按收藏时间')),
PopupMenuItem(value: 'name', child: Text('按名称排序')),
],
),
],
)
PopupMenuButton 会在右上角显示一个菜单按钮,点击后弹出菜单。用户选择排序方式后,重新排序列表并刷新页面。
添加批量操作
如果用户想取消收藏多道菜谱,可以添加批量操作功能:
AppBar(
title: isSelectionMode ? Text('已选择 $selectedCount 项') : Text('我的收藏'),
leading: isSelectionMode ? IconButton(
icon: Icon(Icons.close),
onPressed: () {
// 退出选择模式
},
) : null,
actions: isSelectionMode ? [
IconButton(
icon: Icon(Icons.delete),
onPressed: () {
// 批量取消收藏
},
),
] : [
IconButton(
icon: Icon(Icons.select_all),
onPressed: () {
// 进入选择模式
},
),
],
)
选择模式下,AppBar 显示已选择的数量和批量操作按钮。用户可以勾选多个菜谱,然后一次性取消收藏。
这需要在每个卡片上添加复选框,并用一个 Set 来存储选中的菜谱 ID。
优化网格布局
在不同的屏幕尺寸上,两列布局可能不是最优的。可以根据屏幕宽度动态调整列数:
LayoutBuilder(
builder: (context, constraints) {
int crossAxisCount = 2;
if (constraints.maxWidth > 600) {
crossAxisCount = 3;
} else if (constraints.maxWidth > 900) {
crossAxisCount = 4;
}
return GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: crossAxisCount,
// ...
),
// ...
);
},
)
LayoutBuilder 可以获取父容器的约束,根据宽度判断应该使用几列。这样在大屏设备上可以显示更多列,充分利用屏幕空间。
总结
收藏菜谱功能使用网格布局,每个菜谱显示图片、名称和收藏标记。用户可以快速浏览所有收藏,也可以点击查看详情或取消收藏。
通过合理的布局和交互设计,我们让收藏功能既美观又实用。用户可以方便地管理自己喜欢的菜谱,随时找到想做的菜。
下一篇文章我们将实现浏览历史记录功能,让用户能够回顾之前看过的菜谱。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐


所有评论(0)