在这里插入图片描述

前言

每日小贴士是个很讨喜的功能,每天给用户推送一条垃圾分类的小知识,既不打扰用户,又能潜移默化地传递环保理念。本文将详细介绍如何在Flutter for OpenHarmony环境下实现一个完整的每日小贴士页面,包括数据结构设计、今日推荐算法、卡片样式以及内容运营策略。

技术要点概览

本页面涉及的核心技术点:

  • ListView.builder:高效的列表渲染
  • 日期计算:根据日期确定今日推荐
  • 条件渲染:今日推荐的特殊标记
  • Card组件:卡片式布局设计
  • Emoji图标:生动的视觉表达

数据结构

每条小贴士包含图标和内容:

class DailyTipPage extends StatelessWidget {
  const DailyTipPage({super.key});

  
  Widget build(BuildContext context) {
    final tips = [
      {'icon': '🍶', 'tip': '塑料瓶投放前请清空瓶内液体,压扁后投放更环保'},
      {'icon': '🔋', 'tip': '废电池请单独收集,不要混入其他垃圾'},
      {'icon': '🥬', 'tip': '厨余垃圾请沥干水分后投放'},
      {'icon': '🧻', 'tip': '用过的餐巾纸属于其他垃圾,不可回收'},
      {'icon': '💊', 'tip': '过期药品属于有害垃圾,请连同包装一起投放'},
      {'icon': '👕', 'tip': '旧衣物请清洗干净后打包投放'},
      {'icon': '🍾', 'tip': '玻璃瓶请清洗干净,小心碎片'},
      {'icon': '🦴', 'tip': '大骨头因难以分解,属于其他垃圾'},
    ];

8条小贴士覆盖了常见的分类场景,每条都配了相关的emoji图标,让内容更生动。

内容设计:小贴士的内容要简短实用,一句话说清楚一个知识点。太长了用户不愿意看,太短了又没有价值。

使用Model类管理数据

实际项目中建议使用Model类:

class DailyTip {
  final String icon;
  final String tip;
  final String? category;
  final DateTime? publishDate;
  
  DailyTip({
    required this.icon,
    required this.tip,
    this.category,
    this.publishDate,
  });
  
  factory DailyTip.fromJson(Map<String, dynamic> json) {
    return DailyTip(
      icon: json['icon'],
      tip: json['tip'],
      category: json['category'],
      publishDate: json['publishDate'] != null 
          ? DateTime.parse(json['publishDate']) 
          : null,
    );
  }
}

页面布局

用列表展示所有小贴士,今日推荐的那条会有特殊标记:

    return Scaffold(
      appBar: AppBar(title: const Text('每日小贴士')),
      body: ListView.builder(
        padding: EdgeInsets.all(16.w),
        itemCount: tips.length,
        itemBuilder: (context, index) {
          final tip = tips[index];
          // 计算今日推荐
          final isToday = index == DateTime.now().day % tips.length;

今日推荐算法

isToday的计算逻辑是:用当前日期对小贴士数量取余,这样每天会轮换一条作为"今日推荐"。比如今天是15号,15 % 8 = 7,那第8条(index=7)就是今日推荐。

// 更智能的今日推荐算法
int getTodayTipIndex(int totalTips) {
  final now = DateTime.now();
  // 使用年月日组合生成种子,确保同一天返回相同结果
  final seed = now.year * 10000 + now.month * 100 + now.day;
  final random = Random(seed);
  return random.nextInt(totalTips);
}

卡片样式

今日推荐的卡片有特殊的背景色:

          return Card(
            margin: EdgeInsets.only(bottom: 12.h),
            color: isToday ? AppTheme.primaryColor.withOpacity(0.1) : null,
            child: Padding(
              padding: EdgeInsets.all(16.w),
              child: Row(
                children: [
                  Text(tip['icon']!, style: TextStyle(fontSize: 40.sp)),
                  SizedBox(width: 16.w),

今日推荐的卡片背景是主题色的浅色版本,和其他卡片形成区分,用户一眼就能看出哪条是今天的。

今日推荐标签

今日推荐的卡片上方有个小标签:

                  Expanded(
                    child: Column(
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: [
                        if (isToday)
                          Container(
                            padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 2.h),
                            margin: EdgeInsets.only(bottom: 4.h),
                            decoration: BoxDecoration(
                              color: AppTheme.primaryColor,
                              borderRadius: BorderRadius.circular(4.r),
                            ),
                            child: Text(
                              '今日推荐',
                              style: TextStyle(color: Colors.white, fontSize: 10.sp),
                            ),
                          ),
                        Text(
                          tip['tip']!,
                          style: TextStyle(fontSize: 15.sp, height: 1.5),
                        ),
                      ],
                    ),
                  ),
                ],
              ),
            ),
          );
        },
      ),
    );
  }
}

标签用主题色背景、白色文字,小巧醒目。if (isToday)条件渲染,只有今日推荐才显示这个标签。

每日推荐的实现思路

现在的实现是简单的取余轮换,实际项目中可以做得更智能:

1. 随机推荐

每天随机选一条,避免用户看到重复的内容:

int getTodayTipIndex(int totalTips) {
  final random = Random(DateTime.now().day);
  return random.nextInt(totalTips);
}

用日期作为随机种子,保证同一天多次打开看到的是同一条。

2. 基于用户行为推荐

根据用户的搜索历史,推荐相关的小贴士:

String getPersonalizedTip(List<String> searchHistory, List<DailyTip> tips) {
  // 分析用户搜索历史中的关键词
  final keywords = _extractKeywords(searchHistory);
  
  // 找到匹配的小贴士
  for (var tip in tips) {
    if (keywords.any((kw) => tip.tip.contains(kw))) {
      return tip.tip;
    }
  }
  
  // 没有匹配的,返回今日默认推荐
  return tips[DateTime.now().day % tips.length].tip;
}

3. 未读优先

记录用户看过哪些小贴士,优先推荐没看过的:

class TipReadStatus {
  static final Set<int> _readTips = {};
  
  static void markAsRead(int index) {
    _readTips.add(index);
    _saveToStorage();
  }
  
  static int getUnreadTipIndex(int totalTips) {
    for (int i = 0; i < totalTips; i++) {
      if (!_readTips.contains(i)) {
        return i;
      }
    }
    // 全部看过了,重置
    _readTips.clear();
    return 0;
  }
}

推送通知

每日小贴士可以配合推送通知使用:

import 'package:flutter_local_notifications/flutter_local_notifications.dart';

class TipNotificationService {
  static final _notifications = FlutterLocalNotificationsPlugin();
  
  static Future<void> scheduleDailyTip() async {
    await _notifications.zonedSchedule(
      0,
      '今日环保小贴士',
      getTodayTip(),
      _nextInstanceOfNineAM(),
      const NotificationDetails(
        android: AndroidNotificationDetails(
          'daily_tip',
          '每日小贴士',
          channelDescription: '每天推送一条垃圾分类小知识',
        ),
      ),
      androidAllowWhileIdle: true,
      uiLocalNotificationDateInterpretation:
          UILocalNotificationDateInterpretation.absoluteTime,
      matchDateTimeComponents: DateTimeComponents.time,
    );
  }
  
  static tz.TZDateTime _nextInstanceOfNineAM() {
    final now = tz.TZDateTime.now(tz.local);
    var scheduledDate = tz.TZDateTime(tz.local, now.year, now.month, now.day, 9);
    if (scheduledDate.isBefore(now)) {
      scheduledDate = scheduledDate.add(const Duration(days: 1));
    }
    return scheduledDate;
  }
}

每天定时推送一条小贴士,用户不用打开App也能学到知识。

首页展示

除了单独的页面,今日小贴士也可以在首页展示:

Widget _buildDailyTipCard() {
  final todayTip = tips[DateTime.now().day % tips.length];
  
  return Card(
    margin: EdgeInsets.all(16.w),
    child: InkWell(
      onTap: () => Get.toNamed(Routes.dailyTip),
      borderRadius: BorderRadius.circular(12.r),
      child: Padding(
        padding: EdgeInsets.all(16.w),
        child: Row(
          children: [
            Text(todayTip['icon']!, style: TextStyle(fontSize: 32.sp)),
            SizedBox(width: 12.w),
            Expanded(
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Text('今日小贴士', style: TextStyle(fontSize: 12.sp, color: Colors.grey)),
                  SizedBox(height: 4.h),
                  Text(
                    todayTip['tip']!,
                    style: TextStyle(fontSize: 14.sp),
                    maxLines: 2,
                    overflow: TextOverflow.ellipsis,
                  ),
                ],
              ),
            ),
            Icon(Icons.arrow_forward_ios, size: 16.sp, color: Colors.grey),
          ],
        ),
      ),
    ),
  );
}

在首页放一个小卡片,展示今日小贴士的摘要,点击进入完整列表。

内容运营

小贴士的内容可以持续更新:

1. 节日主题

  • 春节:年货垃圾分类(烟花爆竹、红包、年货包装)
  • 中秋:月饼盒处理、食物残渣
  • 端午:粽叶、粽子绳的分类

2. 季节主题

  • 夏天:饮料瓶、冰棍棒、防晒霜瓶
  • 冬天:取暖设备、厚衣物、暖宝宝

3. 热点主题

  • 环保日:配合世界环境日推送相关内容
  • 地球日:推送地球保护相关知识
List<DailyTip> getSeasonalTips() {
  final month = DateTime.now().month;
  
  if (month >= 6 && month <= 8) {
    // 夏季小贴士
    return summerTips;
  } else if (month >= 12 || month <= 2) {
    // 冬季小贴士
    return winterTips;
  }
  
  return defaultTips;
}

交互增强

1. 点赞功能

final likedTips = <int>{}.obs;

Widget _buildLikeButton(int index) {
  return Obx(() => IconButton(
    icon: Icon(
      likedTips.contains(index) ? Icons.favorite : Icons.favorite_border,
      color: likedTips.contains(index) ? Colors.red : Colors.grey,
    ),
    onPressed: () {
      if (likedTips.contains(index)) {
        likedTips.remove(index);
      } else {
        likedTips.add(index);
      }
    },
  ));
}

2. 分享功能

void shareTip(DailyTip tip) {
  Share.share(
    '${tip.icon} ${tip.tip}\n\n来自垃圾分类指南App',
    subject: '垃圾分类小贴士',
  );
}

性能优化

1. 使用const构造函数

const Text('每日小贴士')
const Icon(Icons.arrow_forward_ios, size: 16)

2. 列表项使用Key

return Card(
  key: ValueKey(index),
  // ...
);

总结

每日小贴士是个小功能,但做好了能持续给用户带来价值,是提升用户粘性的好方法。本文介绍的实现方案包括:

  1. 数据结构设计:图标和内容的组合
  2. 今日推荐算法:基于日期的轮换机制
  3. 卡片样式:今日推荐的特殊标记
  4. 内容运营:节日、季节、热点主题

通过合理的功能设计,可以让用户在使用App的过程中不断学习垃圾分类知识。


欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

Logo

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

更多推荐