在这里插入图片描述

找到心仪的菜谱后,用户需要查看完整的制作步骤和食材清单。今天我们要实现菜谱详情页面,这是整个应用最重要的页面之一,它要清晰地展示菜谱的所有信息。

菜谱详情的设计思路

菜谱详情页面要展示的信息很多:图片、名称、时间、难度、食材、步骤等。如何组织这些信息,让用户能够快速理解并跟着做,是设计的关键。

我采用了分区布局的方式。顶部是大图,吸引用户注意力。图片下方是基本信息,包括名称、时间、难度等。然后是食材列表,最后是制作步骤。

这种布局符合用户的阅读习惯。先看图片判断是不是想要的菜,然后看基本信息评估难度,接着准备食材,最后按步骤制作。整个流程很自然。

创建无状态组件

菜谱详情页面接收一个菜谱 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

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐