Flutter for OpenHarmony 美食烹饪助手 App 实战:菜系分类功能实现
摘要:本文介绍了菜系分类页面的实现思路,采用网格布局展示12种常见菜系。每个菜系卡片包含图标和名称,通过颜色和图标设计增强视觉识别性。使用三列网格布局,正方形卡片保持整齐美观。卡片设计采用圆形图标容器、半透明背景和阴影效果,并预留了点击交互功能。整体设计注重用户快速查找和视觉体验,为后续功能扩展奠定了基础。(149字)

中国饮食文化博大精深,八大菜系各有特色。在美食应用中,按菜系分类是最自然的组织方式之一。今天我们要实现菜系分类页面,让用户能够按照自己喜欢的菜系来浏览菜谱。
菜系分类的设计思路
菜系分类页面要解决的核心问题是:如何让用户快速找到自己想要的菜系?我选择了网格布局,因为它能在一屏内展示多个分类,用户可以快速扫视所有选项。
每个菜系使用不同的颜色和图标来区分,这样用户能快速识别。颜色的选择也有讲究,比如川菜用红色表示辣,粤菜用绿色表示清淡,这些颜色能帮助用户建立视觉记忆。
网格采用三列布局,这个数字在手机屏幕上刚好合适。两列会显得太空,四列又会让每个卡片太小。三列是一个平衡点,既能展示足够的内容,每个卡片又有足够的空间。
定义菜系数据
首先要定义我们支持的菜系列表。我选择了12种常见菜系,包括中国八大菜系和几种国际菜系。
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
class CuisineCategoryPage extends StatelessWidget {
const CuisineCategoryPage({super.key});
Widget build(BuildContext context) {
final categories = [
{'name': '川菜', 'icon': Icons.local_fire_department, 'color': Colors.red},
{'name': '粤菜', 'icon': Icons.restaurant, 'color': Colors.green},
{'name': '鲁菜', 'icon': Icons.dining, 'color': Colors.blue},
{'name': '苏菜', 'icon': Icons.ramen_dining, 'color': Colors.purple},
{'name': '浙菜', 'icon': Icons.rice_bowl, 'color': Colors.teal},
{'name': '闽菜', 'icon': Icons.set_meal, 'color': Colors.orange},
{'name': '湘菜', 'icon': Icons.soup_kitchen, 'color': Colors.deepOrange},
{'name': '徽菜', 'icon': Icons.lunch_dining, 'color': Colors.brown},
{'name': '西餐', 'icon': Icons.fastfood, 'color': Colors.amber},
{'name': '日料', 'icon': Icons.sushi_dining, 'color': Colors.pink},
{'name': '韩餐', 'icon': Icons.food_bank, 'color': Colors.indigo},
{'name': '东南亚', 'icon': Icons.outdoor_grill, 'color': Colors.lime},
];
每个菜系包含三个属性:名称、图标和颜色。使用 Map 来组织数据虽然不如定义一个类那么严谨,但对于这种简单的场景已经够用了。
图标的选择很有讲究。川菜用火焰图标,因为川菜以辣著称。粤菜用餐厅图标,表示精致。日料用寿司图标,这是日料的代表。这些图标能帮助用户快速识别菜系。
颜色的选择也有考虑。红色给人热情、辣的感觉,适合川菜和湘菜。绿色给人清淡、健康的感觉,适合粤菜。这些颜色不仅美观,还能传达菜系的特点。
构建网格布局
菜系分类使用 GridView 来展示,三列布局,正方形卡片。
return Scaffold(
appBar: AppBar(
title: const Text('菜系分类'),
),
body: GridView.builder(
padding: EdgeInsets.all(16.w),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
crossAxisSpacing: 12.w,
mainAxisSpacing: 12.h,
childAspectRatio: 1,
),
itemCount: categories.length,
itemBuilder: (context, index) {
final category = categories[index];
return _buildCategoryCard(
category['name'] as String,
category['icon'] as IconData,
category['color'] as Color,
);
},
),
);
}
crossAxisCount 设置为 3,表示三列。crossAxisSpacing 和 mainAxisSpacing 都设置为 12,让卡片之间有适当的间距。
childAspectRatio 设置为 1,表示宽高比 1:1,也就是正方形。正方形的卡片看起来很整齐,也符合分类导航的设计习惯。
itemCount 使用 categories.length,这样如果以后要添加或删除菜系,只需要修改 categories 列表就行了,不需要改其他代码。
在 itemBuilder 中,我们从 Map 中提取数据,然后传递给 _buildCategoryCard 方法。这里需要做类型转换,因为 Map 的值类型是 dynamic。
设计分类卡片
每个分类卡片包含一个圆形图标和菜系名称。
Widget _buildCategoryCard(String name, IconData icon, Color color) {
return Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12.r),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.1),
blurRadius: 5,
offset: const Offset(0, 2),
),
],
),
卡片使用白色背景、圆角和轻微阴影,这是应用的统一风格。阴影的设置和其他页面保持一致,让整个应用看起来协调。
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
width: 50.w,
height: 50.w,
decoration: BoxDecoration(
color: color.withOpacity(0.1),
borderRadius: BorderRadius.circular(25.r),
),
child: Icon(icon, color: color, size: 28.sp),
),
SizedBox(height: 8.h),
Text(
name,
style: TextStyle(fontSize: 13.sp, fontWeight: FontWeight.bold),
),
],
),
);
}
}
卡片内容垂直居中排列。图标放在一个圆形容器中,容器的背景色是菜系颜色的半透明版本。这样既能展示颜色,又不会太突兀。
圆形容器的宽高都是 50.w,borderRadius 设置为 25.r,正好是宽度的一半,形成完美的圆形。图标颜色使用菜系的主色,大小 28.sp。
菜系名称使用粗体,字号 13.sp。这个字号不大不小,既能清晰显示,又不会占用太多空间。
添加点击交互
虽然现在卡片还不能点击,但我们要为以后的功能预留接口。可以用 GestureDetector 或 InkWell 包裹卡片:
Widget _buildCategoryCard(String name, IconData icon, Color color) {
return InkWell(
onTap: () {
// 跳转到该菜系的菜谱列表
Get.to(() => CuisineRecipesPage(cuisine: name));
},
borderRadius: BorderRadius.circular(12.r),
child: Container(
// ...
),
);
}
InkWell 会在点击时显示水波纹效果,borderRadius 参数让水波纹遵循容器的圆角。这种反馈效果能让用户知道点击被识别了。
点击后跳转到该菜系的菜谱列表页面,传递菜系名称作为参数。这样列表页面就知道要显示哪个菜系的菜谱了。
优化视觉效果
为了让页面更生动,可以给卡片添加一些动画效果。比如点击时稍微缩小,松开时恢复:
class _CategoryCard extends StatefulWidget {
final String name;
final IconData icon;
final Color color;
const _CategoryCard({
required this.name,
required this.icon,
required this.color,
});
State<_CategoryCard> createState() => _CategoryCardState();
}
class _CategoryCardState extends State<_CategoryCard> {
bool _isPressed = false;
Widget build(BuildContext context) {
return GestureDetector(
onTapDown: (_) => setState(() => _isPressed = true),
onTapUp: (_) => setState(() => _isPressed = false),
onTapCancel: () => setState(() => _isPressed = false),
child: AnimatedScale(
scale: _isPressed ? 0.95 : 1.0,
duration: Duration(milliseconds: 100),
child: Container(
// ...
),
),
);
}
}
AnimatedScale 会在 scale 值改变时自动播放动画。按下时 scale 变为 0.95,松开时恢复为 1.0。这种微妙的动画能让交互更有趣。
不过这需要把卡片改成 StatefulWidget,增加了复杂度。对于简单的场景,使用 InkWell 的水波纹效果就够了。
处理不同屏幕尺寸
三列布局在手机上很合适,但在平板等大屏设备上可能显得太空。可以根据屏幕宽度动态调整列数:
LayoutBuilder(
builder: (context, constraints) {
int crossAxisCount = 3;
if (constraints.maxWidth > 600) {
crossAxisCount = 4;
} else if (constraints.maxWidth > 900) {
crossAxisCount = 6;
}
return GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: crossAxisCount,
// ...
),
// ...
);
},
)
LayoutBuilder 可以获取父容器的约束,根据宽度判断应该使用几列。这样在大屏设备上可以显示更多列,充分利用屏幕空间。
添加搜索功能
如果菜系很多,用户可能想快速找到特定的菜系。可以在 AppBar 添加搜索按钮:
AppBar(
title: const Text('菜系分类'),
actions: [
IconButton(
icon: Icon(Icons.search),
onPressed: () {
showSearch(
context: context,
delegate: CuisineSearchDelegate(),
);
},
),
],
)
showSearch 会打开一个搜索界面,CuisineSearchDelegate 定义搜索的行为。用户可以输入关键词,快速找到想要的菜系。
总结
菜系分类页面使用三列网格布局,每个菜系用不同的颜色和图标区分。这种设计清晰直观,用户能快速找到自己想要的菜系。
通过合理的颜色和图标选择,我们不仅让页面美观,还传达了每个菜系的特点。这种设计既实用又有趣,能提升用户的浏览体验。
下一篇文章我们将实现食材分类功能,探讨另一种常见的分类方式。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐



所有评论(0)