在这里插入图片描述

每天为用户推荐新的菜谱,这是保持应用活跃度的重要手段。今日推荐页面不仅要展示精选内容,还要通过设计传达"每日更新"的概念,让用户养成每天打开应用的习惯。

设计每日更新的感觉

今日推荐和普通推荐的区别在于时效性。我希望用户一看到这个页面,就能感受到"这是今天特别为我准备的"。为了达到这个效果,我在页面顶部设计了一个醒目的横幅。

横幅使用渐变色背景,从浅橙色渐变到深橙色,给人温暖、有活力的感觉。中间放置一个太阳图标,象征着"每日"的概念。配合"今日推荐"的大标题和"每天为您推荐新菜谱"的副标题,整体传达出新鲜、每日更新的信息。

这种设计在很多应用中都能看到,比如新闻类应用的"今日头条",音乐类应用的"每日推荐"。用户对这种设计已经形成了认知,看到就知道这是每天更新的内容。

构建页面框架

今日推荐页面使用 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

Logo

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

更多推荐