Flutter for OpenHarmony 美食烹饪助手 App 实战:菜谱详情展示实现
菜谱详情页面设计与实现 摘要:本文介绍了菜谱详情页面的设计思路与实现方法。页面采用分区布局方式,顶部展示菜谱图片,下方依次显示基本信息、食材列表和制作步骤,符合用户阅读习惯。技术实现上使用Flutter框架,构建无状态组件接收菜谱ID参数,通过SingleChildScrollView实现可滚动布局。页面包含编辑和分享功能按钮,信息标签采用圆角容器设计,食材和步骤分别以列表形式清晰展示。实现过程中

找到心仪的菜谱后,用户需要查看完整的制作步骤和食材清单。今天我们要实现菜谱详情页面,这是整个应用最重要的页面之一,它要清晰地展示菜谱的所有信息。
菜谱详情的设计思路
菜谱详情页面要展示的信息很多:图片、名称、时间、难度、食材、步骤等。如何组织这些信息,让用户能够快速理解并跟着做,是设计的关键。
我采用了分区布局的方式。顶部是大图,吸引用户注意力。图片下方是基本信息,包括名称、时间、难度等。然后是食材列表,最后是制作步骤。
这种布局符合用户的阅读习惯。先看图片判断是不是想要的菜,然后看基本信息评估难度,接着准备食材,最后按步骤制作。整个流程很自然。
创建无状态组件
菜谱详情页面接收一个菜谱 ID 作为参数,根据这个 ID 加载数据。
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import 'edit_recipe_page.dart';
class RecipeDetailPage extends StatelessWidget {
final String recipeId;
const RecipeDetailPage({super.key, required this.recipeId});
recipeId 是必需参数,用于标识要显示的菜谱。实际开发中,应该根据这个 ID 从数据库或网络加载菜谱数据。
这里使用 StatelessWidget,因为页面内容相对固定。如果需要管理收藏状态等,可以改成 StatefulWidget。
构建页面结构
页面使用 SingleChildScrollView 包裹,让内容可以滚动。
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('菜谱详情'),
actions: [
IconButton(
icon: const Icon(Icons.edit),
onPressed: () => Get.to(() => EditRecipePage(recipeId: recipeId)),
),
IconButton(
icon: const Icon(Icons.share),
onPressed: () {},
),
],
),
AppBar 右侧放了两个按钮:编辑和分享。编辑按钮跳转到编辑页面,分享按钮触发分享功能。
这两个功能都很常用,所以放在 AppBar 上,让用户容易找到。编辑功能只有菜谱的创建者才能使用,实际开发中需要做权限判断。
body: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
height: 250.h,
color: Colors.orange.shade100,
child: Center(child: Icon(Icons.restaurant, size: 100.sp, color: Colors.orange)),
),
顶部是菜谱图片。高度设置为 250.h,占据屏幕的大约三分之一。实际开发中应该显示真实的图片,这里用图标代替。
图片使用 Container 而不是 Image,是为了方便设置背景色。如果图片加载失败,至少还有背景色,不会显示空白。
Padding(
padding: EdgeInsets.all(16.w),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('美味菜谱', style: TextStyle(fontSize: 24.sp, fontWeight: FontWeight.bold)),
SizedBox(height: 12.h),
Row(
children: [
_buildInfoChip(Icons.timer, '30分钟'),
SizedBox(width: 12.w),
_buildInfoChip(Icons.people, '2-3人'),
SizedBox(width: 12.w),
_buildInfoChip(Icons.local_fire_department, '简单'),
],
),
图片下方是菜谱名称和基本信息。名称使用大号粗体,字号 24.sp,这是一级标题的标准大小。
基本信息使用标签的形式展示,包括时间、份量和难度。三个标签水平排列,用 SizedBox 分隔。
实现信息标签
信息标签使用圆角容器包裹图标和文字。
Widget _buildInfoChip(IconData icon, String text) {
return Container(
padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 6.h),
decoration: BoxDecoration(
color: Colors.orange.shade50,
borderRadius: BorderRadius.circular(20.r),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(icon, size: 16.sp, color: Colors.orange),
SizedBox(width: 4.w),
Text(text, style: TextStyle(fontSize: 12.sp)),
],
),
);
}
标签使用橙色的浅色背景,圆角半径 20.r,形成胶囊形状。padding 设置为水平 12.w、垂直 6.h,让内容有适当的空间。
图标和文字水平排列,mainAxisSize 设置为 min,让容器只占据必要的空间。图标大小 16.sp,颜色是橙色。文字大小 12.sp,颜色是默认的黑色。
展示食材列表
食材列表使用简单的文本列表展示。
SizedBox(height: 20.h),
Text('食材', style: TextStyle(fontSize: 18.sp, fontWeight: FontWeight.bold)),
SizedBox(height: 12.h),
...List.generate(5, (i) => Padding(
padding: EdgeInsets.only(bottom: 8.h),
child: Text('• 食材 ${i + 1}'),
)),
食材标题使用粗体,字号 18.sp,这是二级标题的标准大小。标题和列表之间有 12.h 的间距。
每个食材前面加一个圆点,表示这是列表项。食材之间有 8.h 的间距,让列表不会显得太拥挤。
实际开发中,食材应该包含名称和用量,比如"猪肉 300g"。可以使用 Row 来排列名称和用量,让它们对齐。
展示制作步骤
制作步骤是菜谱最重要的部分,需要清晰地展示每一步。
SizedBox(height: 20.h),
Text('步骤', style: TextStyle(fontSize: 18.sp, fontWeight: FontWeight.bold)),
SizedBox(height: 12.h),
...List.generate(5, (i) => Padding(
padding: EdgeInsets.only(bottom: 12.h),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
CircleAvatar(
radius: 15.r,
backgroundColor: Colors.orange,
child: Text('${i + 1}', style: const TextStyle(color: Colors.white)),
),
SizedBox(width: 12.w),
Expanded(child: Text('步骤 ${i + 1} 的详细说明...')),
],
),
)),
],
),
),
],
),
),
步骤标题和食材标题类似,使用粗体和 18.sp 字号。每个步骤包含一个序号和说明文字。
序号使用 CircleAvatar 组件,这是一个圆形头像组件,这里用来显示数字。半径 15.r,背景色是橙色,文字是白色。
说明文字使用 Expanded 包裹,让它占据剩余空间。crossAxisAlignment 设置为 start,让序号和文字顶部对齐。
步骤之间有 12.h 的间距,比食材的间距稍大一些,因为步骤的内容更多,需要更多的空间来区分。
添加底部操作栏
页面底部可以添加一个操作栏,包含收藏、开始烹饪等按钮。
bottomNavigationBar: Container(
padding: EdgeInsets.all(16.w),
child: ElevatedButton(
onPressed: () {},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.orange,
minimumSize: Size(double.infinity, 50.h),
),
child: const Text('开始烹饪', style: TextStyle(color: Colors.white)),
),
),
);
}
}
bottomNavigationBar 通常用于放置底部导航栏,但这里我们用它来放置操作按钮。这样按钮会固定在底部,不会随着内容滚动。
按钮使用橙色背景,白色文字,宽度占满整个屏幕,高度 50.h。点击后可以进入烹饪模式,比如显示计时器、勾选完成的步骤等。
添加收藏功能
用户可能想收藏这道菜谱,可以在 AppBar 添加收藏按钮:
IconButton(
icon: Icon(isFavorite ? Icons.favorite : Icons.favorite_border),
onPressed: () {
setState(() {
isFavorite = !isFavorite;
});
},
)
收藏状态用一个布尔变量存储。收藏时显示实心的心形图标,未收藏时显示空心的。点击切换状态。
这需要把 StatelessWidget 改成 StatefulWidget,用一个变量来存储收藏状态。实际开发中,收藏状态应该保存到数据库。
添加图片查看
用户可能想放大查看菜谱图片,可以给图片添加点击事件:
GestureDetector(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ImageViewPage(imageUrl: recipe.imageUrl),
),
);
},
child: Container(
// 图片容器
),
)
点击图片后跳转到图片查看页面,可以放大、缩放、滑动查看。这在菜谱应用中很常用,因为用户需要看清楚菜品的样子。
添加评论功能
用户可能想看看其他人对这道菜的评价,可以在步骤下方添加评论区:
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: EdgeInsets.all(16.w),
child: Text('评论', style: TextStyle(fontSize: 18.sp, fontWeight: FontWeight.bold)),
),
...comments.map((comment) => ListTile(
leading: CircleAvatar(child: Text(comment.userName[0])),
title: Text(comment.userName),
subtitle: Text(comment.content),
)),
],
)
评论使用 ListTile 展示,包含用户头像、用户名和评论内容。用户可以点击查看完整的评论,也可以添加自己的评论。
添加相关推荐
在页面底部可以推荐相关的菜谱,比如同一菜系的、同样食材的等:
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: EdgeInsets.all(16.w),
child: Text('相关推荐', style: TextStyle(fontSize: 18.sp, fontWeight: FontWeight.bold)),
),
SizedBox(
height: 200.h,
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: 10,
itemBuilder: (context, index) {
return _buildRecommendCard(index);
},
),
),
],
)
推荐使用横向滚动的卡片列表,和首页的推荐菜谱类似。这能帮助用户发现更多感兴趣的菜谱,增加应用的粘性。
添加营养信息
对于关注健康的用户,可以显示菜谱的营养信息:
Container(
margin: EdgeInsets.all(16.w),
padding: EdgeInsets.all(16.w),
decoration: BoxDecoration(
color: Colors.orange.shade50,
borderRadius: BorderRadius.circular(12.r),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('营养信息', style: TextStyle(fontSize: 16.sp, fontWeight: FontWeight.bold)),
SizedBox(height: 8.h),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
_buildNutritionItem('热量', '500 kcal'),
_buildNutritionItem('蛋白质', '30 g'),
_buildNutritionItem('脂肪', '20 g'),
],
),
],
),
)
营养信息使用卡片展示,背景色是橙色的浅色版本。包含热量、蛋白质、脂肪等主要营养成分。
优化加载体验
菜谱数据可能需要从网络加载,可以显示加载状态:
FutureBuilder<Recipe>(
future: loadRecipe(recipeId),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(child: CircularProgressIndicator());
}
if (snapshot.hasError) {
return Center(child: Text('加载失败'));
}
final recipe = snapshot.data!;
return _buildContent(recipe);
},
)
FutureBuilder 根据 Future 的状态显示不同的内容。加载中显示进度指示器,加载失败显示错误信息,加载成功显示菜谱内容。
总结
菜谱详情页面使用分区布局,清晰地展示菜谱的所有信息。顶部大图吸引注意力,基本信息帮助评估难度,食材列表和制作步骤指导烹饪。
通过合理的信息组织和交互设计,我们让菜谱详情页面既美观又实用。用户可以轻松理解菜谱内容,跟着步骤制作美食。
至此,我们已经完成了菜谱库模块的主要功能。下一篇文章我们将开始实现厨房工具模块,提供更多实用的烹饪辅助功能。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐



所有评论(0)