在这里插入图片描述

每日任务是激励用户持续学习的重要功能,通过设置小目标和奖励机制提升用户活跃度。本文介绍如何实现一个每日任务页面,包括进度展示、任务列表和连续签到奖励。

Provider状态管理

获取全局状态数据:

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

  
  Widget build(BuildContext context) {
    final appProvider = Provider.of<AppProvider>(context);
    
    final tasks = [
      {'title': '完成1个基础课程', 'points': 10, 'completed': appProvider.todayLearned >= 1},
      {'title': '完成3个课程', 'points': 30, 'completed': appProvider.todayLearned >= 3},
      {'title': '完成5个课程', 'points': 50, 'completed': appProvider.todayLearned >= 5},
      {'title': '完成一次测验', 'points': 20, 'completed': false},
      {'title': '收藏一个课程', 'points': 5, 'completed': appProvider.favorites.isNotEmpty},
      {'title': '学习10分钟', 'points': 15, 'completed': false},
    ];

使用Provider.of获取AppProvider实例,访问全局状态如今日学习数量、收藏列表等。任务完成状态根据Provider中的数据动态判断,比如todayLearned >= 1判断是否完成1个课程。这种数据驱动的方式让任务状态自动更新。

页面布局

构建整体的滚动布局:

    return Scaffold(
      appBar: AppBar(title: const Text('每日任务')),
      body: SingleChildScrollView(
        padding: EdgeInsets.all(16.w),
        child: Column(
          children: [
            _buildProgressCard(context, appProvider),
            SizedBox(height: 16.h),
            _buildTaskList(tasks),
            SizedBox(height: 16.h),
            _buildRewardSection(),
          ],
        ),
      ),
    );
  }

SingleChildScrollView包裹,内容过多时可以滚动。Column纵向排列三个区域:进度卡片、任务列表和奖励区域。SizedBox控制区域间距,让布局疏密有致。这种分区设计让信息层次清晰。

进度卡片

构建顶部的进度展示:

  Widget _buildProgressCard(BuildContext context, AppProvider appProvider) {
    final completedTasks = appProvider.todayLearned;
    final totalTasks = appProvider.dailyGoal;
    
    return Card(
      child: Padding(
        padding: EdgeInsets.all(20.w),
        child: Column(
          children: [
            CircularPercentIndicator(
              radius: 60.r,
              lineWidth: 10.w,
              percent: (completedTasks / totalTasks).clamp(0.0, 1.0),

从Provider获取已完成和总目标数量,计算完成百分比。CircularPercentIndicator是第三方组件,显示圆形进度条。clamp(0.0, 1.0)限制百分比在0-1之间,避免超过100%或为负数。这种可视化展示比纯数字更直观。

进度条中心内容

显示完成数量:

              center: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  Text(
                    '$completedTasks',
                    style: TextStyle(fontSize: 28.sp, fontWeight: FontWeight.bold),
                  ),
                  Text('/ $totalTasks', style: TextStyle(fontSize: 14.sp, color: Colors.grey)),
                ],
              ),
              progressColor: const Color(0xFF00897B),
              backgroundColor: Colors.grey[200]!,
            ),
            SizedBox(height: 16.h),

进度条中心显示已完成数量和总目标,用大字号突出已完成数,小字号灰色显示总数。progressColor设为主题色,backgroundColor设为浅灰色。这种对比设计让进度一目了然。

进度描述

显示进度标题和鼓励文字:

            Text(
              '今日学习进度',
              style: TextStyle(fontSize: 16.sp, fontWeight: FontWeight.w500),
            ),
            SizedBox(height: 8.h),
            Text(
              completedTasks >= totalTasks ? '🎉 太棒了!今日目标已完成' : '继续加油,还差${totalTasks - completedTasks}个任务',
              style: TextStyle(fontSize: 14.sp, color: Colors.grey[600]),
            ),
          ],
        ),
      ),
    );
  }

标题用中等字号和半粗体,描述文字根据完成情况动态变化。完成目标显示庆祝emoji和鼓励文字,未完成显示剩余任务数。这种情感化设计增强了用户的成就感和动力。

任务列表

构建任务列表区域:

  Widget _buildTaskList(List<Map<String, dynamic>> tasks) {
    return Card(
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Padding(
            padding: EdgeInsets.all(16.w),
            child: Text(
              '任务列表',
              style: TextStyle(fontSize: 16.sp, fontWeight: FontWeight.bold),
            ),
          ),

任务列表用Card包裹,顶部显示"任务列表"标题。crossAxisAlignment.start让内容左对齐,符合阅读习惯。标题用粗体突出显示,与任务项形成视觉层次

任务列表项

使用map生成任务项:

          ...tasks.map((task) => ListTile(
            leading: Icon(
              task['completed'] as bool ? Icons.check_circle : Icons.radio_button_unchecked,
              color: task['completed'] as bool ? Colors.green : Colors.grey,
            ),
            title: Text(
              task['title'] as String,
              style: TextStyle(
                decoration: task['completed'] as bool ? TextDecoration.lineThrough : null,
                color: task['completed'] as bool ? Colors.grey : Colors.black,
              ),
            ),

...展开运算符将map结果插入列表。左侧图标根据完成状态显示:已完成显示绿色对勾,未完成显示灰色空心圆。标题文字已完成时添加删除线并变灰,未完成时正常显示。这种视觉反馈让用户清楚任务状态。

积分标签

右侧显示任务奖励:

            trailing: Container(
              padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.h),
              decoration: BoxDecoration(
                color: Colors.amber.withOpacity(0.2),
                borderRadius: BorderRadius.circular(12.r),
              ),
              child: Text(
                '+${task['points']}积分',
                style: TextStyle(fontSize: 12.sp, color: Colors.amber[800]),
              ),
            ),
          )).toList(),
        ],
      ),
    );
  }

积分标签用金色半透明背景和深金色文字,圆角设计更柔和。金色代表奖励和价值,符合用户对积分的心理预期+号强调这是获得的奖励,激励用户完成任务。

连续签到奖励

构建签到奖励区域:

  Widget _buildRewardSection() {
    return Card(
      child: Padding(
        padding: EdgeInsets.all(16.w),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(
              '连续签到奖励',
              style: TextStyle(fontSize: 16.sp, fontWeight: FontWeight.bold),
            ),
            SizedBox(height: 12.h),

签到奖励用Card包裹,标题用粗体显示。crossAxisAlignment.start让内容左对齐。这个区域展示连续签到的进度,鼓励用户每天打卡

签到天数展示

使用List.generate生成7天:

            Row(
              mainAxisAlignment: MainAxisAlignment.spaceAround,
              children: List.generate(7, (index) {
                final isCompleted = index < 3;
                return Column(
                  children: [
                    Container(
                      width: 36.w,
                      height: 36.w,
                      decoration: BoxDecoration(
                        color: isCompleted ? const Color(0xFF00897B) : Colors.grey[200],
                        shape: BoxShape.circle,
                      ),
                      child: Icon(
                        isCompleted ? Icons.check : Icons.card_giftcard,
                        color: isCompleted ? Colors.white : Colors.grey,
                        size: 20.sp,
                      ),
                    ),

List.generate生成7个签到圆圈,mainAxisAlignment.spaceAround让它们均匀分布。已签到显示主题色背景和白色对勾,未签到显示灰色背景和礼物图标。这种进度可视化让用户清楚签到情况。

天数标签

显示第几天:

                    SizedBox(height: 4.h),
                    Text('第${index + 1}天', style: TextStyle(fontSize: 10.sp)),
                  ],
                );
              }),
            ),
          ],
        ),
      ),
    );
  }
}

每个圆圈下方显示"第X天"文字,用小字号避免占用过多空间。index + 1将索引转换为天数(从1开始)。这种设计让用户知道每个圆圈代表第几天的签到

CircularPercentIndicator的优势

使用第三方进度条组件:

CircularPercentIndicator(
  radius: 60.r,
  lineWidth: 10.w,
  percent: (completedTasks / totalTasks).clamp(0.0, 1.0),
  center: Column(...),
  progressColor: const Color(0xFF00897B),
  backgroundColor: Colors.grey[200]!,
)

CircularPercentIndicator提供圆形进度条功能,支持自定义半径、线宽、颜色和中心内容。自己实现需要使用CustomPaint绘制,代码复杂。使用现成组件可以快速开发,且效果专业。

clamp方法的作用

限制数值范围:

percent: (completedTasks / totalTasks).clamp(0.0, 1.0),

clamp(0.0, 1.0)确保百分比在0-1之间。如果completedTasks为0,结果是0;如果超过totalTasks,结果是1。这种边界处理避免了除零错误和超过100%的异常显示。

展开运算符

简化列表插入:

...tasks.map((task) => ListTile(...)).toList(),

...展开运算符将map返回的列表展开,插入到父列表中。这比先创建列表再用addAll添加更简洁。展开运算符是Dart的语法糖,让代码更优雅。

条件表达式

根据状态显示不同内容:

task['completed'] as bool ? Icons.check_circle : Icons.radio_button_unchecked
task['completed'] as bool ? TextDecoration.lineThrough : null
completedTasks >= totalTasks ? '🎉 太棒了!今日目标已完成' : '继续加油,还差${totalTasks - completedTasks}个任务'

三元运算符? :根据条件返回不同值,一行代码实现条件逻辑。这种简洁的写法if-else更适合简单的条件判断,代码可读性高。

List.generate的应用

生成重复元素:

List.generate(7, (index) {
  final isCompleted = index < 3;
  return Column(...);
})

List.generate生成指定数量的元素,回调函数接收索引参数。这比手动创建7个元素更简洁,也便于修改数量。index < 3模拟前3天已签到,实际项目中应该从服务器获取真实数据。

颜色的语义化

不同颜色传达不同信息:

Colors.green,              // 已完成/成功
Colors.grey,               // 未完成/待处理
Colors.amber,              // 积分/奖励
Color(0xFF00897B),         // 主题色/进度

绿色代表完成和成功,灰色代表待处理,金色代表奖励,主题色代表进度。这种颜色语义化符合用户认知习惯,无需文字说明就能理解含义。

删除线的应用

标识已完成任务:

TextStyle(
  decoration: task['completed'] as bool ? TextDecoration.lineThrough : null,
  color: task['completed'] as bool ? Colors.grey : Colors.black,
)

已完成的任务添加删除线并变灰,视觉上表示"已划掉"。这是待办事项应用的经典设计,用户一看就懂。删除线配合灰色,双重视觉提示更明确。

Provider的响应式更新

状态变化自动更新UI:

final appProvider = Provider.of<AppProvider>(context);
{'title': '完成1个基础课程', 'points': 10, 'completed': appProvider.todayLearned >= 1},

任务完成状态依赖Provider中的数据,当todayLearned变化时,Provider.of会通知组件重建,任务状态自动更新。这种响应式编程让数据与UI保持同步,无需手动刷新。

响应式布局

使用flutter_screenutil适配屏幕:

radius: 60.r,
lineWidth: 10.w,
fontSize: 28.sp,
padding: EdgeInsets.all(16.w),

.r用于圆角半径,.w用于宽度和线宽,.sp用于字号。这些单位会根据屏幕尺寸自动缩放,确保在不同设备上比例一致。一套代码适配所有屏幕。

小结

每日任务页面通过圆形进度条直观展示学习进度,任务列表清晰标识完成状态和积分奖励。连续签到奖励鼓励用户每天打卡,情感化的文字提示增强用户动力。整体设计注重可视化和激励机制,有效提升用户活跃度。

任务完成检测

实时检测任务完成状态:

void _checkTaskCompletion() {
  for (var task in tasks) {
    if (task['type'] == 'learn_count') {
      task['completed'] = appProvider.todayLearned >= task['target'];
    } else if (task['type'] == 'quiz') {
      task['completed'] = appProvider.todayQuizCompleted;
    }
  }
  setState(() {});
}

根据不同任务类型检查完成条件,学习类任务检查学习数量,测验类任务检查是否完成测验。检测逻辑应该在数据变化时自动触发。

积分奖励发放

完成任务后发放积分:

void _claimReward(Map task) async {
  if (!task['completed'] || task['claimed']) return;
  
  final points = task['points'] as int;
  await appProvider.addPoints(points);
  
  task['claimed'] = true;
  setState(() {});
  
  ScaffoldMessenger.of(context).showSnackBar(
    SnackBar(content: Text('获得${points}积分!')),
  );
}

检查任务是否完成且未领取,调用Provider增加积分,标记为已领取,显示提示信息。这种即时反馈增强用户成就感。

每日重置逻辑

每天零点重置任务状态:

void _checkDailyReset() async {
  final prefs = await SharedPreferences.getInstance();
  final lastDate = prefs.getString('lastTaskDate');
  final today = DateTime.now().toString().substring(0, 10);
  
  if (lastDate != today) {
    await appProvider.resetDailyTasks();
    await prefs.setString('lastTaskDate', today);
  }
}

对比上次任务日期和今天日期,如果不同则重置任务。这个检查应该在应用启动时执行,确保任务状态正确。

连续签到计算

计算连续签到天数:

int _calculateStreakDays() {
  final signInDates = appProvider.signInHistory;
  if (signInDates.isEmpty) return 0;
  
  int streak = 1;
  for (int i = signInDates.length - 1; i > 0; i--) {
    final diff = signInDates[i].difference(signInDates[i-1]).inDays;
    if (diff == 1) {
      streak++;
    } else {
      break;
    }
  }
  return streak;
}

从最近的签到日期往前遍历,如果相邻两天相差1天则连续,否则中断。这个算法可以准确计算连续天数。

签到奖励规则

不同天数给予不同奖励:

int _getStreakReward(int days) {
  if (days >= 30) return 500;
  if (days >= 14) return 200;
  if (days >= 7) return 100;
  if (days >= 3) return 50;
  return 10;
}

连续签到天数越多,奖励越丰厚。这种递增奖励机制激励用户长期坚持。

任务推送通知

定时提醒用户完成任务:

void _scheduleTaskReminder() async {
  final notification = FlutterLocalNotificationsPlugin();
  
  await notification.zonedSchedule(
    0,
    '每日任务提醒',
    '还有任务未完成,快来领取积分吧!',
    _nextInstanceOf20PM(),
    const NotificationDetails(...),
    uiLocalNotificationDateInterpretation: ...,
    matchDateTimeComponents: DateTimeComponents.time,
  );
}

使用本地通知在每天晚上8点提醒用户完成任务。推送通知可以有效提升用户活跃度。

任务数据统计

统计任务完成情况:

Map<String, int> _getTaskStatistics() {
  return {
    'totalTasks': tasks.length,
    'completedTasks': tasks.where((t) => t['completed']).length,
    'totalPoints': tasks.fold(0, (sum, t) => sum + (t['points'] as int)),
    'earnedPoints': tasks
        .where((t) => t['completed'])
        .fold(0, (sum, t) => sum + (t['points'] as int)),
  };
}

计算总任务数、已完成数、总积分和已获得积分。这些统计数据可以显示在页面顶部,让用户了解整体情况。

任务难度调整

根据用户水平动态调整任务难度:

List<Map> _generateDailyTasks(int userLevel) {
  if (userLevel < 5) {
    return [
      {'title': '完成1个课程', 'target': 1, 'points': 10},
      {'title': '学习5分钟', 'target': 5, 'points': 5},
    ];
  } else {
    return [
      {'title': '完成5个课程', 'target': 5, 'points': 50},
      {'title': '完成2次测验', 'target': 2, 'points': 40},
    ];
  }
}

新手用户任务简单,高级用户任务更有挑战性。这种个性化任务提升用户体验。


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

Logo

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

更多推荐