Flutter for OpenHarmony 美食烹饪助手 App 实战:今日推荐功能实现
本文介绍了菜谱应用"今日推荐"页面的设计实现。重点包括:1)通过渐变橙色横幅、太阳图标和"今日推荐"标题营造每日更新感;2)采用SingleChildScrollView+ListView嵌套结构处理滚动;3)设计横向卡片布局,包含图片区、标签系统和内容信息。页面整体风格统一,通过视觉元素强化"每日精选"概念,旨在培养用户每日查看习惯,提

每天为用户推荐新的菜谱,这是保持应用活跃度的重要手段。今日推荐页面不仅要展示精选内容,还要通过设计传达"每日更新"的概念,让用户养成每天打开应用的习惯。
设计每日更新的感觉
今日推荐和普通推荐的区别在于时效性。我希望用户一看到这个页面,就能感受到"这是今天特别为我准备的"。为了达到这个效果,我在页面顶部设计了一个醒目的横幅。
横幅使用渐变色背景,从浅橙色渐变到深橙色,给人温暖、有活力的感觉。中间放置一个太阳图标,象征着"每日"的概念。配合"今日推荐"的大标题和"每天为您推荐新菜谱"的副标题,整体传达出新鲜、每日更新的信息。
这种设计在很多应用中都能看到,比如新闻类应用的"今日头条",音乐类应用的"每日推荐"。用户对这种设计已经形成了认知,看到就知道这是每天更新的内容。
构建页面框架
今日推荐页面使用 SingleChildScrollView 包裹整个内容,因为页面包含横幅和列表两部分。
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
class DailyRecommendationPage extends StatelessWidget {
const DailyRecommendationPage({super.key});
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('今日推荐'),
),
body: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildDailyBanner(),
SizedBox(height: 20.h),
Padding(
padding: EdgeInsets.symmetric(horizontal: 16.w),
child: Text(
'为您精选',
style: TextStyle(fontSize: 18.sp, fontWeight: FontWeight.bold),
),
),
SizedBox(height: 12.h),
页面结构很清晰,顶部是横幅,然后是一个小标题"为您精选",最后是推荐列表。Column 的 crossAxisAlignment 设置为 start,让所有内容左对齐。
使用 SingleChildScrollView 而不是 ListView 是因为页面顶部有固定的横幅。如果直接用 ListView,横幅也会跟着滚动,这不是我们想要的效果。当然,也可以用 CustomScrollView 配合 SliverAppBar 来实现,但那样会更复杂。
设计醒目的横幅
横幅是今日推荐页面的灵魂,它要足够醒目,让用户一眼就能感受到"今日"的概念。
Widget _buildDailyBanner() {
return Container(
height: 200.h,
margin: EdgeInsets.all(16.w),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [Colors.orange.shade300, Colors.orange.shade600],
),
borderRadius: BorderRadius.circular(12.r),
),
横幅高度设置为 200.h,比首页的横幅稍高一些。这是因为今日推荐页面的横幅承载了更多信息,需要更大的空间。
渐变色从 orange.shade300 到 orange.shade600,这个渐变比较明显,能产生强烈的视觉冲击。相比首页横幅的 orange 到 deepOrange,这个渐变更柔和一些,因为我们不希望它太刺眼。
圆角半径 12.r 和应用的整体风格保持一致。margin 设置为 16.w,让横幅和屏幕边缘保持距离。
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.wb_sunny, size: 60.sp, color: Colors.white),
SizedBox(height: 10.h),
Text(
'今日推荐',
style: TextStyle(
color: Colors.white,
fontSize: 24.sp,
fontWeight: FontWeight.bold,
),
),
SizedBox(height: 5.h),
Text(
'每天为您推荐新菜谱',
style: TextStyle(color: Colors.white, fontSize: 14.sp),
),
],
),
),
);
}
横幅内容垂直居中排列。wb_sunny 是太阳图标,大小 60.sp,颜色白色。这个图标很形象地表达了"每日"的概念,就像每天升起的太阳一样。
标题"今日推荐"使用白色粗体,字号 24.sp,比较大。副标题"每天为您推荐新菜谱"字号 14.sp,比较小,作为辅助说明。
两行文字之间的间距 5.h 比较小,让它们看起来是一个整体。图标和标题之间的间距 10.h 稍大一些,形成视觉分组。
实现推荐列表
横幅下方是推荐列表,展示今日精选的菜谱。
ListView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
padding: EdgeInsets.symmetric(horizontal: 16.w),
itemCount: 10,
itemBuilder: (context, index) {
return _buildDailyItem(index);
},
),
],
),
),
);
}
这里有两个关键设置。shrinkWrap 设置为 true,让 ListView 的高度适应内容,而不是占据所有可用空间。physics 设置为 NeverScrollableScrollPhysics,禁用 ListView 自己的滚动,让外层的 SingleChildScrollView 来处理滚动。
这种嵌套滚动的处理方式在 Flutter 中很常见。如果不禁用内层的滚动,会出现滚动冲突,用户体验很差。
itemCount 设置为 10,表示每天推荐 10 道菜谱。这个数字不能太多,否则就失去了"精选"的意义;也不能太少,否则用户会觉得内容不够丰富。10 是一个比较合适的数字。
设计推荐卡片
每个推荐项使用横向卡片布局,左侧是图片,右侧是信息。
Widget _buildDailyItem(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),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.1),
blurRadius: 5,
offset: const Offset(0, 2),
),
],
),
卡片的样式和其他页面保持一致,白色背景、圆角、轻微阴影。margin 只设置底部间距,让卡片之间有适当的分隔。
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),
),
SizedBox(width: 12.w),
图片区域是一个正方形,宽高都是 80。这个尺寸比推荐菜谱页面的图片小一些,因为今日推荐的卡片整体更紧凑。
圆角半径 8.r,比外层容器的 12.r 小一些。这种大小圆角的搭配能产生层次感,让设计更精致。
添加标签系统
今日推荐的特色是使用标签来突出菜谱的特点。
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'今日推荐 ${index + 1}',
style: TextStyle(fontSize: 15.sp, fontWeight: FontWeight.bold),
),
SizedBox(height: 6.h),
Text(
'营养均衡,美味可口',
style: TextStyle(fontSize: 12.sp, color: Colors.grey),
),
SizedBox(height: 6.h),
Row(
children: [
_buildTag('简单'),
SizedBox(width: 8.w),
_buildTag('30分钟'),
],
),
],
),
),
],
),
);
}
右侧内容使用 Expanded 包裹,占据剩余空间。Column 垂直排列标题、描述和标签。
标题使用粗体,字号 15.sp。描述使用灰色,字号 12.sp,作为辅助信息。标签行使用 Row 横向排列,两个标签之间用 SizedBox 分隔。
标签的设计很重要,它能快速传达菜谱的关键信息。用户扫一眼就能知道这道菜是简单还是困难,需要多长时间。
Widget _buildTag(String text) {
return Container(
padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.h),
decoration: BoxDecoration(
color: Colors.orange.shade50,
borderRadius: BorderRadius.circular(4.r),
),
child: Text(
text,
style: TextStyle(fontSize: 10.sp, color: Colors.orange),
),
);
}
}
标签是一个小的圆角矩形,背景色使用浅橙色,文字使用深橙色。padding 设置得比较小,让标签紧凑一些。
圆角半径 4.r,比较小,让标签看起来更精致。文字大小 10.sp,也比较小,因为标签只是辅助信息。
这种标签设计在很多应用中都能看到,它能有效地组织信息,让界面更清晰。用户也习惯了这种设计,看到就知道这是关键信息。
实现每日更新逻辑
虽然现在使用的是模拟数据,但我们要考虑真实场景下的每日更新逻辑。
可以在页面加载时检查日期,如果是新的一天,就重新获取推荐数据:
class DailyRecommendationPage extends StatefulWidget {
const DailyRecommendationPage({super.key});
State<DailyRecommendationPage> createState() => _DailyRecommendationPageState();
}
class _DailyRecommendationPageState extends State<DailyRecommendationPage> {
DateTime? _lastUpdateDate;
void initState() {
super.initState();
_checkAndUpdate();
}
void _checkAndUpdate() {
final today = DateTime.now();
if (_lastUpdateDate == null ||
_lastUpdateDate!.day != today.day) {
_loadDailyRecommendations();
_lastUpdateDate = today;
}
}
void _loadDailyRecommendations() {
// 从服务器获取今日推荐
}
}
这个逻辑会在页面初始化时检查日期。如果是新的一天,或者还没有加载过数据,就调用 _loadDailyRecommendations 获取新数据。
日期可以存储在本地,这样即使用户关闭应用再打开,也能记住上次更新的日期。可以使用 shared_preferences 来存储。
添加刷新功能
用户可能想手动刷新今日推荐,看看有没有新内容。可以添加下拉刷新功能:
RefreshIndicator(
onRefresh: () async {
await _loadDailyRecommendations();
},
child: SingleChildScrollView(
// ...
),
)
RefreshIndicator 包裹 SingleChildScrollView,当用户下拉时会触发刷新。在 onRefresh 回调中重新获取数据。
注意 onRefresh 必须返回一个 Future,这样 RefreshIndicator 才知道什么时候停止显示加载指示器。如果数据获取是异步的,直接返回那个 Future 就行。
优化用户体验
今日推荐页面要给用户一种"每天都有新内容"的感觉。除了实际更新内容,还可以通过一些设计细节来强化这种感觉。
比如可以在横幅上显示日期:
Text(
'${DateTime.now().month}月${DateTime.now().day}日',
style: TextStyle(color: Colors.white, fontSize: 14.sp),
)
这样用户一看就知道这是今天的推荐。日期的显示也提醒用户"明天还会有新内容",增加用户的期待感。
还可以添加一个小动画,让横幅在页面加载时从上方滑入:
AnimatedContainer(
duration: Duration(milliseconds: 500),
curve: Curves.easeOut,
// ...
)
这种小动画能让页面更生动,也能吸引用户的注意力。不过要注意不要过度使用动画,否则会让应用显得花哨。
处理空状态
如果今日推荐暂时没有内容,要显示友好的提示:
if (recommendations.isEmpty) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.wb_sunny, size: 64, color: Colors.grey),
SizedBox(height: 16),
Text('今日推荐准备中...', style: TextStyle(color: Colors.grey)),
SizedBox(height: 8),
Text('请稍后再来查看', style: TextStyle(fontSize: 12, color: Colors.grey)),
],
),
);
}
空状态页面使用太阳图标,和横幅的图标呼应。文字说明当前状态,给用户明确的反馈。
这种处理方式比显示空白页面要好得多。即使出现异常情况,用户也知道发生了什么,不会感到困惑。
总结
今日推荐页面通过醒目的横幅和精心设计的列表,传达出"每日更新"的概念。横幅使用渐变色和太阳图标,让用户一眼就能感受到新鲜感。列表使用标签系统,快速展示菜谱的关键信息。
这种设计不仅美观,还能有效地引导用户养成每天打开应用的习惯。配合真实的每日更新逻辑,能大大提升应用的活跃度。
下一篇文章我们将实现菜系分类功能,探讨如何设计一个清晰的分类导航系统。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐



所有评论(0)