Flutter for OpenHarmony 美食烹饪助手 App 实战:我的菜谱管理实现
本文介绍了如何实现一个菜谱管理页面,采用列表布局展示用户创建的菜谱。每个列表项包含图片、名称和创建时间,支持点击查看详情和长按操作菜单。页面使用无状态组件构建,包含圆角卡片布局和响应式设计。详细说明了列表项UI实现、点击跳转详情页、长按显示操作菜单(编辑、分享、删除)以及删除确认对话框等功能。通过合理的间距、圆角和色彩搭配,打造了清晰易用的菜谱管理界面。

每个热爱烹饪的人都有自己的拿手菜,也会不断创作新的菜谱。今天我们要实现我的菜谱管理功能,让用户能够查看、编辑和管理自己创建的所有菜谱。
我的菜谱的设计思路
我的菜谱页面要解决的核心问题是:如何让用户方便地管理大量的菜谱?我选择了列表布局,因为列表能展示更多信息,也方便滚动浏览。
每个菜谱项显示图片、名称和创建时间。图片能让用户快速识别菜谱,创建时间能帮助用户回忆。这些信息足够用户判断是哪道菜,又不会显得太拥挤。
列表项可以点击进入详情页,也可以长按显示操作菜单。这种设计在文件管理应用中很常见,用户很容易理解。点击是查看,长按是操作,符合用户的使用习惯。
创建无状态组件
我的菜谱页面的内容相对固定,使用 StatelessWidget 就够了。
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
class MyRecipesPage extends StatelessWidget {
const MyRecipesPage({super.key});
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('我的菜谱')),
body: ListView.builder(
padding: EdgeInsets.all(16.w),
itemCount: 20,
itemBuilder: (context, index) => _buildItem(index),
),
);
}
页面结构很简单,就是一个 AppBar 和一个 ListView。AppBar 只有标题,没有其他按钮,保持简洁。
ListView.builder 的 padding 设置为 16.w,让列表内容不要紧贴屏幕边缘。itemCount 现在是固定的 20,实际开发中应该根据数据动态设置。
实现列表项
每个列表项是一个卡片,包含图片、名称和创建时间。
Widget _buildItem(int index) {
return Container(
margin: EdgeInsets.only(bottom: 12.h),
padding: EdgeInsets.all(12.w),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12.r),
),
卡片使用白色背景和圆角。margin 设置为 bottom: 12.h,让卡片之间有间距。padding 设置为 12.w,让内容不要紧贴边缘。
这里没有设置阴影,因为背景色已经是浅灰色了,白色卡片本身就有足够的对比度。如果背景是白色,就需要添加阴影来区分卡片和背景。
child: Row(
children: [
Container(
width: 80.w,
height: 80.h,
decoration: BoxDecoration(
color: Colors.orange.shade100,
borderRadius: BorderRadius.circular(8.r),
),
child: Icon(Icons.restaurant, size: 35.sp, color: Colors.orange),
),
左侧是菜谱图片的占位符。实际开发中应该显示真实的图片,这里用图标代替。容器大小 80x80,背景色是橙色的浅色版本。
圆角设置为 8.r,比卡片的圆角小一些,形成层次感。图标大小 35.sp,颜色是橙色,和背景色搭配。
SizedBox(width: 12.w),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('我的菜谱 ${index + 1}', style: TextStyle(fontSize: 15.sp, fontWeight: FontWeight.bold)),
SizedBox(height: 6.h),
Text('创建于 2024-01-${(index % 28) + 1}', style: TextStyle(fontSize: 11.sp, color: Colors.grey)),
],
),
),
],
),
);
}
}
右侧是菜谱信息。使用 Expanded 让这部分占据剩余空间。菜谱名称使用粗体,字号 15.sp。
创建时间使用灰色,字号 11.sp,表示这是次要信息。时间使用公式 (index % 28) + 1 来生成不同的日期,让数据看起来更真实。
添加点击交互
点击列表项应该跳转到菜谱详情页:
Widget _buildItem(int index) {
return GestureDetector(
onTap: () {
Get.to(() => RecipeDetailPage(recipeId: 'recipe_$index'));
},
child: Container(
// ...
),
);
}
使用 GestureDetector 包裹整个卡片,点击时跳转到详情页。传递菜谱 ID 作为参数,详情页会根据这个 ID 加载对应的菜谱数据。
也可以使用 InkWell 代替 GestureDetector,这样点击时会有水波纹效果。但要注意 InkWell 需要在 Material 组件下才能显示水波纹。
添加长按菜单
长按列表项应该显示操作菜单,比如编辑、删除、分享等:
GestureDetector(
onTap: () {
// 跳转到详情页
},
onLongPress: () {
_showActionMenu(context, index);
},
child: Container(
// ...
),
)
onLongPress 回调在用户长按时触发。我们调用 _showActionMenu 方法显示操作菜单。
void _showActionMenu(BuildContext context, int index) {
showModalBottomSheet(
context: context,
builder: (context) {
return SafeArea(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
ListTile(
leading: Icon(Icons.edit),
title: Text('编辑'),
onTap: () {
Navigator.pop(context);
Get.to(() => EditRecipePage(recipeId: 'recipe_$index'));
},
),
ListTile(
leading: Icon(Icons.share),
title: Text('分享'),
onTap: () {
Navigator.pop(context);
// 分享菜谱
},
),
ListTile(
leading: Icon(Icons.delete, color: Colors.red),
title: Text('删除', style: TextStyle(color: Colors.red)),
onTap: () {
Navigator.pop(context);
_confirmDelete(context, index);
},
),
],
),
);
},
);
}
showModalBottomSheet 会从底部弹出一个菜单。SafeArea 确保内容不会被刘海屏或底部横条遮挡。
菜单包含三个选项:编辑、分享和删除。每个选项使用 ListTile,包含图标和文字。点击选项时,先关闭菜单,然后执行对应的操作。
删除选项使用红色,表示这是危险操作。点击后会弹出确认对话框,避免误删。
实现删除确认
删除是危险操作,需要用户确认:
void _confirmDelete(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('删除', style: TextStyle(color: Colors.red)),
),
],
);
},
);
}
showDialog 显示一个对话框。对话框包含标题、内容和两个按钮。标题是"确认删除",内容说明删除的后果。
两个按钮分别是"取消"和"删除"。取消按钮只是关闭对话框,删除按钮执行删除操作并显示提示。
删除按钮使用红色,再次强调这是危险操作。这种双重确认能有效避免误删。
添加空状态
如果用户还没有创建任何菜谱,需要显示空状态:
if (recipes.isEmpty) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.restaurant_menu, size: 64.sp, color: Colors.grey),
SizedBox(height: 16.h),
Text('还没有创建菜谱', style: TextStyle(color: Colors.grey)),
SizedBox(height: 8.h),
ElevatedButton(
onPressed: () => Get.to(() => CreateRecipePage()),
child: Text('创建第一道菜谱'),
),
],
),
);
}
空状态要友好,不要让用户觉得是出错了。使用图标和文字说明,并提供一个按钮引导用户创建菜谱。
图标使用灰色,大小 64.sp,比较大,起到装饰作用。文字也是灰色,表示这是提示信息。按钮使用应用的主题色,吸引用户点击。
添加搜索功能
如果菜谱很多,可以添加搜索功能:
AppBar(
title: TextField(
decoration: InputDecoration(
hintText: '搜索我的菜谱...',
border: InputBorder.none,
prefixIcon: Icon(Icons.search),
),
onChanged: (value) {
// 过滤菜谱列表
},
),
)
搜索框放在 AppBar 的 title 位置,用户输入关键词时实时过滤列表。这需要把 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。
总结
我的菜谱管理功能使用列表布局,每个菜谱显示图片、名称和创建时间。点击可以查看详情,长按可以显示操作菜单。
通过合理的交互设计,我们让菜谱管理既方便又安全。用户可以轻松查看和编辑菜谱,删除操作有双重确认,避免误删。
下一篇文章我们将实现收藏菜谱功能,让用户能够收藏喜欢的菜谱。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐

所有评论(0)