flutter_for_openharmony口腔护理app实战+我的成就实现

前言

成就系统是提升用户粘性的有效手段。通过设置各种成就目标,激励用户坚持口腔护理习惯,当用户达成目标时给予成就奖励,可以有效提升用户的参与感和成就感。

本文将介绍如何在 Flutter 中实现一个带有进度展示的成就系统页面。

功能设计

成就页面需要实现以下功能:

  • 统计概览:展示已解锁、进行中的成就数量和完成率
  • 分类展示:将成就分为已解锁和进行中两类
  • 进度显示:未解锁的成就显示当前进度
  • 视觉设计:使用图标和颜色区分成就状态

页面基础结构

成就页面使用 StatelessWidget 实现:

dart 复制代码
class AchievementPage extends StatelessWidget {
  const AchievementPage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('我的成就')),
      body: Consumer<AppProvider>(
        builder: (context, provider, _) {
          final unlocked = provider.achievements
              .where((a) => a.isUnlocked).toList();
          final locked = provider.achievements
              .where((a) => !a.isUnlocked).toList();

使用 Consumer 监听数据变化,将成就按解锁状态分类。

统计概览卡片

页面顶部展示统计信息:

dart 复制代码
          return SingleChildScrollView(
            padding: const EdgeInsets.all(16),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Container(
                  padding: const EdgeInsets.all(20),
                  decoration: BoxDecoration(
                    gradient: const LinearGradient(
                      colors: [Color(0xFF26A69A), Color(0xFF4DB6AC)],
                    ),
                    borderRadius: BorderRadius.circular(16),
                  ),
                  child: Row(
                    mainAxisAlignment: MainAxisAlignment.spaceAround,
                    children: [
                      _buildStatItem('已解锁', '${unlocked.length}'),
                      Container(width: 1, height: 40, color: Colors.white30),
                      _buildStatItem('进行中', '${locked.length}'),
                      Container(width: 1, height: 40, color: Colors.white30),
                      _buildStatItem('完成率', 
                          '${(unlocked.length / provider.achievements.length * 100).toInt()}%'),
                    ],
                  ),
                ),

使用渐变背景的卡片展示三项统计数据,白色分隔线区分各项。

统计项组件:

dart 复制代码
Widget _buildStatItem(String label, String value) {
  return Column(
    children: [
      Text(value, style: const TextStyle(fontSize: 24, 
          fontWeight: FontWeight.bold, color: Colors.white)),
      const SizedBox(height: 4),
      Text(label, style: const TextStyle(color: Colors.white70)),
    ],
  );
}

数值使用大号白色加粗字体,标签使用半透明白色。

已解锁成就列表

展示已解锁的成就:

dart 复制代码
                const SizedBox(height: 24),
                if (unlocked.isNotEmpty) ...[
                  const Text('已解锁', 
                      style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
                  const SizedBox(height: 12),
                  ...unlocked.map((a) => _buildAchievementCard(a, true)),
                  const SizedBox(height: 24),
                ],

使用条件渲染,只有存在已解锁成就时才显示该分类。

进行中成就列表

展示进行中的成就:

dart 复制代码
                if (locked.isNotEmpty) ...[
                  const Text('进行中', 
                      style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
                  const SizedBox(height: 12),
                  ...locked.map((a) => _buildAchievementCard(a, false)),
                ],
              ],
            ),
          );
        },
      ),
    );
  }

进行中的成就会显示进度条。

成就卡片组件

成就卡片根据状态显示不同样式:

dart 复制代码
Widget _buildAchievementCard(dynamic achievement, bool isUnlocked) {
  return Container(
    margin: const EdgeInsets.only(bottom: 12),
    padding: const EdgeInsets.all(16),
    decoration: BoxDecoration(
      color: Colors.white,
      borderRadius: BorderRadius.circular(12),
      border: isUnlocked ? Border.all(color: Colors.amber, width: 2) : null,
    ),
    child: Row(
      children: [
        Container(
          width: 50,
          height: 50,
          decoration: BoxDecoration(
            color: isUnlocked 
                ? Colors.amber.withOpacity(0.2) 
                : Colors.grey.shade200,
            shape: BoxShape.circle,
          ),
          child: Center(
            child: Text(
              achievement.icon,
              style: TextStyle(
                fontSize: 24,
                color: isUnlocked ? null : Colors.grey,
              ),
            ),
          ),
        ),

已解锁的成就使用金色边框和背景,未解锁的使用灰色。成就图标使用 emoji 表情。

成就名称和描述:

dart 复制代码
        const SizedBox(width: 16),
        Expanded(
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              Text(
                achievement.name,
                style: TextStyle(
                  fontWeight: FontWeight.bold,
                  fontSize: 16,
                  color: isUnlocked ? Colors.black : Colors.grey,
                ),
              ),
              const SizedBox(height: 4),
              Text(
                achievement.description,
                style: TextStyle(color: Colors.grey.shade600, fontSize: 13),
              ),

未解锁的成就名称使用灰色,已解锁的使用黑色。

进度条显示:

dart 复制代码
              if (!isUnlocked) ...[
                const SizedBox(height: 8),
                LinearPercentIndicator(
                  lineHeight: 6,
                  percent: achievement.progress / achievement.target,
                  backgroundColor: Colors.grey.shade200,
                  progressColor: const Color(0xFF26A69A),
                  barRadius: const Radius.circular(3),
                  padding: EdgeInsets.zero,
                  trailing: Padding(
                    padding: const EdgeInsets.only(left: 8),
                    child: Text(
                      '${achievement.progress}/${achievement.target}',
                      style: TextStyle(color: Colors.grey.shade600, fontSize: 12),
                    ),
                  ),
                ),
              ],
            ],
          ),
        ),

使用 percent_indicator 包的 LinearPercentIndicator 组件显示进度,右侧显示当前进度和目标值。

已解锁标识:

dart 复制代码
        if (isUnlocked)
          const Icon(Icons.check_circle, color: Colors.amber, size: 28),
      ],
    ),
  );
}

已解锁的成就右侧显示金色勾选图标。

数据模型定义

成就的数据模型:

dart 复制代码
class Achievement {
  final String id;
  final String name;
  final String description;
  final String icon;
  final int target;
  final int progress;
  final bool isUnlocked;
  final DateTime? unlockedDate;

  Achievement({
    String? id,
    required this.name,
    required this.description,
    required this.icon,
    required this.target,
    this.progress = 0,
    this.isUnlocked = false,
    this.unlockedDate,
  }) : id = id ?? DateTime.now().millisecondsSinceEpoch.toString();
}

模型包含名称、描述、图标、目标值、当前进度、解锁状态和解锁时间等字段。

Provider 数据管理

AppProvider 中管理成就数据:

dart 复制代码
List<Achievement> _achievements = [];
List<Achievement> get achievements => _achievements;

void initAchievements() {
  _achievements = [
    Achievement(
      name: '初次刷牙',
      description: '完成第一次刷牙记录',
      icon: '🦷',
      target: 1,
      progress: 1,
      isUnlocked: true,
    ),
    Achievement(
      name: '坚持一周',
      description: '连续7天完成刷牙',
      icon: '🔥',
      target: 7,
      progress: 5,
    ),
    Achievement(
      name: '护理达人',
      description: '累计刷牙100次',
      icon: '🏆',
      target: 100,
      progress: 45,
    ),
  ];
}

初始化成就列表,包含已解锁和未解锁的成就。

成就解锁逻辑

检查并解锁成就:

dart 复制代码
void checkAchievements() {
  for (int i = 0; i < _achievements.length; i++) {
    final a = _achievements[i];
    if (!a.isUnlocked && a.progress >= a.target) {
      _achievements[i] = Achievement(
        id: a.id,
        name: a.name,
        description: a.description,
        icon: a.icon,
        target: a.target,
        progress: a.progress,
        isUnlocked: true,
        unlockedDate: DateTime.now(),
      );
    }
  }
  notifyListeners();
}

当进度达到目标时自动解锁成就。

更新成就进度

更新特定成就的进度:

dart 复制代码
void updateAchievementProgress(String id, int progress) {
  final index = _achievements.indexWhere((a) => a.id == id);
  if (index != -1) {
    final old = _achievements[index];
    _achievements[index] = Achievement(
      id: old.id,
      name: old.name,
      description: old.description,
      icon: old.icon,
      target: old.target,
      progress: progress,
      isUnlocked: progress >= old.target,
      unlockedDate: progress >= old.target ? DateTime.now() : null,
    );
    notifyListeners();
  }
}

更新进度时自动检查是否达成目标。

成就解锁通知

解锁成就时显示通知:

dart 复制代码
void showAchievementUnlocked(BuildContext context, Achievement achievement) {
  showDialog(
    context: context,
    builder: (ctx) => AlertDialog(
      content: Column(
        mainAxisSize: MainAxisSize.min,
        children: [
          Container(
            width: 80,
            height: 80,
            decoration: BoxDecoration(
              color: Colors.amber.withOpacity(0.2),
              shape: BoxShape.circle,
            ),
            child: Center(
              child: Text(achievement.icon, style: const TextStyle(fontSize: 40)),
            ),
          ),
          const SizedBox(height: 16),
          const Text('🎉 成就解锁!', 
              style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold)),
          const SizedBox(height: 8),
          Text(achievement.name, 
              style: const TextStyle(fontSize: 18)),
          const SizedBox(height: 4),
          Text(achievement.description, 
              style: TextStyle(color: Colors.grey.shade600)),
        ],
      ),
      actions: [
        ElevatedButton(
          onPressed: () => Navigator.pop(ctx),
          style: ElevatedButton.styleFrom(backgroundColor: Colors.amber),
          child: const Text('太棒了!'),
        ),
      ],
    ),
  );
}

使用对话框展示解锁的成就信息。

成就分类

可以按类型对成就进行分类:

dart 复制代码
final achievementCategories = {
  '日常护理': ['初次刷牙', '坚持一周', '护理达人'],
  '知识学习': ['知识达人', '百科全书'],
  '社交互动': ['分享达人', '邀请好友'],
};

按分类展示成就,让用户更清晰地了解成就体系。

成就详情页

点击成就查看详情:

dart 复制代码
void _showAchievementDetail(BuildContext context, Achievement achievement) {
  showModalBottomSheet(
    context: context,
    shape: const RoundedRectangleBorder(
      borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
    ),
    builder: (context) => Container(
      padding: const EdgeInsets.all(24),
      child: Column(
        mainAxisSize: MainAxisSize.min,
        children: [
          Container(
            width: 80,
            height: 80,
            decoration: BoxDecoration(
              color: achievement.isUnlocked 
                  ? Colors.amber.withOpacity(0.2) 
                  : Colors.grey.shade200,
              shape: BoxShape.circle,
            ),
            child: Center(
              child: Text(achievement.icon, style: const TextStyle(fontSize: 40)),
            ),
          ),
          const SizedBox(height: 16),
          Text(achievement.name, 
              style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold)),
          const SizedBox(height: 8),
          Text(achievement.description, 
              style: TextStyle(color: Colors.grey.shade600)),
          const SizedBox(height: 16),
          if (achievement.isUnlocked && achievement.unlockedDate != null)
            Text('解锁时间:${DateFormat('yyyy-MM-dd').format(achievement.unlockedDate!)}',
                style: TextStyle(color: Colors.grey.shade500, fontSize: 12)),
          if (!achievement.isUnlocked)
            Text('进度:${achievement.progress}/${achievement.target}',
                style: TextStyle(color: Colors.grey.shade500)),
        ],
      ),
    ),
  );
}

详情弹窗展示成就的完整信息。

成就分享功能

分享已解锁的成就:

dart 复制代码
void _shareAchievement(Achievement achievement) {
  Share.share(
    '我在口腔护理App中解锁了"${achievement.name}"成就!${achievement.description}',
    subject: '成就分享',
  );
}

用户可以分享自己的成就到社交平台。

成就排行榜

展示成就排行:

dart 复制代码
Container(
  padding: const EdgeInsets.all(16),
  child: Column(
    crossAxisAlignment: CrossAxisAlignment.start,
    children: [
      const Text('成就排行', 
          style: TextStyle(fontWeight: FontWeight.bold)),
      const SizedBox(height: 12),
      ...topUsers.asMap().entries.map((entry) => ListTile(
        leading: CircleAvatar(
          backgroundColor: entry.key < 3 
              ? [Colors.amber, Colors.grey, Colors.brown][entry.key] 
              : Colors.grey.shade300,
          child: Text('${entry.key + 1}'),
        ),
        title: Text(entry.value.name),
        trailing: Text('${entry.value.achievementCount}个成就'),
      )),
    ],
  ),
)

展示解锁成就最多的用户排行。

空状态处理

没有成就时显示空状态:

dart 复制代码
if (provider.achievements.isEmpty) {
  return Center(
    child: Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        Icon(Icons.emoji_events, size: 64, color: Colors.grey.shade300),
        const SizedBox(height: 16),
        Text('暂无成就', style: TextStyle(color: Colors.grey.shade500)),
        const SizedBox(height: 8),
        Text('开始你的口腔护理之旅吧',
             style: TextStyle(color: Colors.grey.shade400, fontSize: 12)),
      ],
    ),
  );
}

友好的空状态提示。

总结

本文详细介绍了口腔护理 App 中成就系统功能的实现。通过进度展示和视觉反馈,我们构建了一个激励用户的成就页面。核心技术点包括:

  • 使用渐变背景的统计卡片
  • 通过 LinearPercentIndicator 显示进度
  • 使用金色边框和图标标识已解锁成就
  • 条件渲染处理不同状态的成就

成就系统是提升用户粘性的有效手段,希望本文的实现对你有所帮助。

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

相关推荐
九狼JIULANG1 天前
Flutter SSE 流式响应用 Dio 实现 OpenAI 兼容接口的逐 Token 输出
flutter
恋猫de小郭1 天前
你是不是觉得 R8 很讨厌,但 Android 为什么选择 R8 ?也许你对 R8 还不够了解
android·前端·flutter
前端不太难1 天前
Flutter 页面切换后为什么会“状态丢失”或“状态常驻”?
flutter·状态模式
松叶似针1 天前
Flutter三方库适配OpenHarmony【secure_application】— pubspec.yaml 多平台配置与依赖管理
flutter·harmonyos
阿林来了2 天前
Flutter三方库适配OpenHarmony【flutter_speech】— MethodChannel 双向通信实现
flutter·harmonyos
阿林来了2 天前
Flutter三方库适配OpenHarmony【flutter_speech】— 单元测试与集成测试
flutter·单元测试·集成测试·harmonyos
松叶似针2 天前
Flutter三方库适配OpenHarmony【secure_application】— 性能影响与优化策略
flutter·harmonyos
松叶似针2 天前
Flutter三方库适配OpenHarmony【secure_application】— 测试策略与用例设计
flutter·harmonyos
心之语歌2 天前
flutter 父子组件互相调用方法,值更新
前端·javascript·flutter