时间感知器应用


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

一、项目概述

运行效果图
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

1.1 应用简介

时间感知器是一款独特的时间感知训练工具,旨在帮助用户培养"不看表就能感知时间"的能力。在现代社会,我们过度依赖时钟和手机来获取时间信息,逐渐丧失了对时间流逝的自然感知能力。这款应用通过游戏化的猜测机制,让用户重新找回对时间的敏感度。

应用以深邃的紫色为主色调,营造出神秘而专注的氛围。用户通过滚轮选择器猜测当前时间,系统会即时计算偏差并给予评分和等级评定。随着持续练习,用户的时间感知能力会逐步提升,从"时间新手"成长为"时间大师"。

1.2 核心功能

功能模块 功能描述 实现方式
时间猜测 不看表猜测当前时间 滚轮选择器
评分系统 根据偏差计算得分 算法评分
等级评定 五级时间感知等级 枚举映射
训练指导 时间感训练技巧 图文卡片
历史记录 猜测历史回顾 时间线列表
统计分析 准确度趋势图表 自定义绘制

1.3 等级体系

序号 等级名称 图标 颜色 平均偏差要求
1 时间大师 🏆 #FFD700 ≤5分钟
2 时间专家 #7C4DFF ≤10分钟
3 时间达人 🌟 #00BCD4 ≤20分钟
4 时间学徒 #4CAF50 ≤30分钟
5 时间新手 🌱 #9E9E9E >30分钟

1.4 技术栈

技术领域 技术选型 版本要求
开发框架 Flutter >= 3.0.0
编程语言 Dart >= 2.17.0
设计规范 Material Design 3 -
状态管理 setState -
动画系统 AnimationController -
目标平台 鸿蒙OS / Web API 21+

1.5 项目结构

lib/
└── main_time_sense.dart
    ├── TimeSenseApp               # 应用入口
    ├── SenseLevel                 # 感知等级枚举
    ├── TimeGuess                  # 猜测记录模型
    ├── TimeSenseHomePage          # 主页面(底部导航)
    ├── _buildGuessPage            # 猜测页面
    ├── _buildTrainingPage         # 训练页面
    ├── _buildHistoryPage          # 历史页面
    ├── _buildStatsPage            # 统计页面
    └── TrendChartPainter          # 趋势图表绘制器

二、系统架构

2.1 整体架构图

Data Layer

Presentation Layer

主页面
TimeSenseHomePage

猜测页

训练页

历史页

统计页

时间选择器

结果展示

训练技巧

等级进度

历史卡片

概览统计

趋势图表

SenseLevel
等级枚举

TimeGuess
猜测模型

2.2 类图设计

uses

manages

uses

TimeSenseApp

+Widget build()

«enumeration»

SenseLevel

+master 时间大师

+expert 时间专家

+advanced 时间达人

+intermediate 时间学徒

+beginner 时间新手

+String label

+String emoji

+Color color

+int stars

+fromDeviation(double) : SenseLevel

TimeGuess

+String id

+DateTime guessedTime

+DateTime actualTime

+int deviationMinutes

+int score

+SenseLevel level

+String deviationText

+String directionText

TimeSenseHomePage

-int _selectedIndex

-List<TimeGuess> _guessHistory

-TimeOfDay? _selectedHour

-TimeOfDay? _selectedMinute

-bool _hasGuessed

-TimeGuess? _lastGuess

-AnimationController _pulseController

-AnimationController _resultController

-AnimationController _celebrationController

+void _submitGuess()

+void _resetGuess()

+int _calculateScore(int)

+Map<String,dynamic> _getStatistics()

TrendChartPainter

-List<double> data

+void paint(Canvas, Size)

+bool shouldRepaint()

2.3 页面导航流程

猜时间

训练

历史

统计

再来一次

切换页面

应用启动

主页面

底部导航

猜测页面

训练页面

历史页面

统计页面

选择时间

确认猜测

显示结果

用户操作

查看训练技巧

查看等级进度

2.4 猜测流程时序图

历史记录 结果展示 评分系统 猜测页 时间选择器 用户 历史记录 结果展示 评分系统 猜测页 时间选择器 用户 滚动选择小时 滚动选择分钟 返回选择的时间 点击确认猜测 获取当前实际时间 计算时间偏差 计算得分(0-100) 评定等级 返回结果 显示结果动画 保存猜测记录 展示最终评定

三、核心模块设计

3.1 数据模型设计

3.1.1 感知等级枚举 (SenseLevel)
enum SenseLevel {
  master('时间大师', '🏆', Color(0xFFFFD700), 5),
  expert('时间专家', '⭐', Color(0xFF7C4DFF), 4),
  advanced('时间达人', '🌟', Color(0xFF00BCD4), 3),
  intermediate('时间学徒', '✨', Color(0xFF4CAF50), 2),
  beginner('时间新手', '🌱', Color(0xFF9E9E9E), 1);

  final String label;
  final String emoji;
  final Color color;
  final int stars;

  const SenseLevel(this.label, this.emoji, this.color, this.stars);

  static SenseLevel fromDeviation(double avgDeviation) {
    if (avgDeviation <= 5) return SenseLevel.master;
    if (avgDeviation <= 10) return SenseLevel.expert;
    if (avgDeviation <= 20) return SenseLevel.advanced;
    if (avgDeviation <= 30) return SenseLevel.intermediate;
    return SenseLevel.beginner;
  }
}
3.1.2 猜测记录模型 (TimeGuess)
class TimeGuess {
  final String id;              // 唯一标识
  final DateTime guessedTime;   // 猜测的时间
  final DateTime actualTime;    // 实际时间
  final int deviationMinutes;   // 偏差分钟数
  final int score;              // 得分(0-100)
  final SenseLevel level;       // 评定等级
  
  String get deviationText {
    if (deviationMinutes == 0) return '完美!';
    return '${deviationMinutes}分钟';
  }
  
  String get directionText {
    if (deviationMinutes == 0) return '精准命中';
    // 判断猜快了还是猜慢了
  }
}
3.1.3 评分算法
int _calculateScore(int deviationMinutes) {
  if (deviationMinutes == 0) return 100;
  if (deviationMinutes <= 5) return 95;
  if (deviationMinutes <= 10) return 85;
  if (deviationMinutes <= 15) return 75;
  if (deviationMinutes <= 20) return 65;
  if (deviationMinutes <= 30) return 50;
  if (deviationMinutes <= 45) return 35;
  return 20;
}
3.1.4 等级分布
70% 10% 10% 5% 5% 等级评定标准分布 时间大师(≤5分钟) 时间专家(≤10分钟) 时间达人(≤20分钟) 时间学徒(≤30分钟) 时间新手(>30分钟)

3.2 页面结构设计

3.2.1 主页面布局

TimeSenseHomePage

IndexedStack

猜测页

训练页

历史页

统计页

NavigationBar

猜时间 Tab

训练 Tab

历史 Tab

统计 Tab

3.2.2 猜测页面结构

猜测页面

SliverAppBar

内容区域

渐变背景

脉冲动画图标

应用标题

时间选择器

确认按钮

结果卡片

快速统计

小时滚轮

分钟滚轮

3.2.3 结果展示结构

结果卡片

等级展示

时间对比

偏差信息

操作按钮

等级图标

等级名称

星级评分

猜测时间

实际时间

偏差数值

方向指示

3.3 动画系统设计

开始选择时间

点击确认

计算完成

动画结束

再来一次

空闲状态

选择中

确认猜测

结果动画

展示结果

缩放+透明度动画
偏差≤10分钟时触发庆祝动画


四、UI设计规范

4.1 配色方案

应用采用深邃的紫色为主色调,营造神秘专注的氛围:

颜色类型 色值 用途
主色 #7C4DFF 导航、按钮、强调元素
渐变起始 #7C4DFF (40%透明) 头部渐变
渐变结束 #7C4DFF (20%透明) 头部渐变
大师色 #FFD700 金色
专家色 #7C4DFF 紫色
达人色 #00BCD4 青色
学徒色 #4CAF50 绿色
新手色 #9E9E9E 灰色

4.2 字体规范

元素 字号 字重 颜色
应用标题 28px Bold #FFFFFF
等级名称 20-24px Bold 等级色
时间数字 28-32px Bold #000000
滚轮数字 20-32px 动态 动态
偏差数值 20px Bold 等级色
辅助文字 12-14px Regular #757575

4.3 组件规范

4.3.1 时间滚轮选择器
        ┌─────────────────────────────────┐
        │                                 │
        │      08                        │
        │    ┌─────────┐                 │
        │    │   09    │  ← 选中项       │
        │    └─────────┘                 │
        │      10                        │
        │                                 │
        │         :                       │
        │                                 │
        │      28                        │
        │    ┌─────────┐                 │
        │    │   29    │  ← 选中项       │
        │    └─────────┘                 │
        │      30                        │
        │                                 │
        └─────────────────────────────────┘
4.3.2 结果卡片
┌─────────────────────────────────────────────┐
│                                             │
│        🏆  时间大师                         │
│            ⭐⭐⭐⭐⭐                        │
│                                             │
│    ┌──────────┐      ┌──────────┐         │
│    │ 你的猜测 │      │ 实际时间 │         │
│    │   09:28  │  →   │   09:30  │         │
│    └──────────┘      └──────────┘         │
│                                             │
│         偏差:2分钟   猜慢了               │
│                                             │
│           [再来一次]                        │
│                                             │
└─────────────────────────────────────────────┘
4.3.3 等级进度卡片
┌─────────────────────────────────────────────┐
│  等级进度                                    │
│                                             │
│  🏆 时间大师    ⭐⭐⭐⭐⭐    ← 当前        │
│  ⭐ 时间专家    ⭐⭐⭐⭐                     │
│  🌟 时间达人    ⭐⭐⭐                       │
│  ✨ 时间学徒    ⭐⭐                         │
│  🌱 时间新手    ⭐                           │
│                                             │
└─────────────────────────────────────────────┘

五、核心功能实现

5.1 时间选择器实现

Widget _buildTimeWheel({
  required String label,
  required int value,
  required int maxValue,
  required Function(int) onChanged,
}) {
  return Column(
    children: [
      SizedBox(
        height: 180,
        width: 80,
        child: ListWheelScrollView.useDelegate(
          itemExtent: 50,
          perspective: 0.005,
          diameterRatio: 1.5,
          physics: const FixedExtentScrollPhysics(),
          onSelectedItemChanged: onChanged,
          child: ListWheelChildBuilderDelegate(
            childCount: maxValue + 1,
            builder: (context, index) {
              final isSelected = index == value;
              return Center(
                child: Text(
                  index.toString().padLeft(2, '0'),
                  style: TextStyle(
                    fontSize: isSelected ? 32 : 20,
                    fontWeight: isSelected ? FontWeight.bold : FontWeight.normal,
                    color: isSelected
                        ? Theme.of(context).colorScheme.primary
                        : Colors.grey[400],
                  ),
                ),
              );
            },
          ),
        ),
      ),
      const SizedBox(height: 8),
      Text(label, style: TextStyle(fontSize: 14, color: Colors.grey[600])),
    ],
  );
}

5.2 猜测提交与评分

void _submitGuess() {
  if (_selectedHour == null || _selectedMinute == null) return;

  final now = DateTime.now();
  final guessedTime = DateTime(
    now.year,
    now.month,
    now.day,
    _selectedHour!.hour,
    _selectedMinute!.minute,
  );

  final actualTime = now;
  final guessedMinutes = guessedTime.hour * 60 + guessedTime.minute;
  final actualMinutes = actualTime.hour * 60 + actualTime.minute;
  final deviation = (guessedMinutes - actualMinutes).abs();

  final guess = TimeGuess(
    id: DateTime.now().millisecondsSinceEpoch.toString(),
    guessedTime: guessedTime,
    actualTime: actualTime,
    deviationMinutes: deviation,
    score: _calculateScore(deviation),
    level: SenseLevel.fromDeviation(deviation.toDouble()),
  );

  setState(() {
    _lastGuess = guess;
    _hasGuessed = true;
    _guessHistory.insert(0, guess);
  });

  _resultController.forward(from: 0);
  if (deviation <= 10) {
    _celebrationController.forward(from: 0);
  }
}

5.3 统计数据计算

Map<String, dynamic> _getStatistics() {
  if (_guessHistory.isEmpty) {
    return {
      'total': 0,
      'avgDeviation': 0.0,
      'bestDeviation': 0,
      'worstDeviation': 0,
      'avgScore': 0.0,
      'level': SenseLevel.beginner,
      'accuracyTrend': <double>[],
    };
  }

  final deviations = _guessHistory.map((g) => g.deviationMinutes).toList();
  final avgDeviation = deviations.reduce((a, b) => a + b) / deviations.length;
  final bestDeviation = deviations.reduce((a, b) => a < b ? a : b);
  final worstDeviation = deviations.reduce((a, b) => a > b ? a : b);
  final avgScore = _guessHistory.map((g) => g.score).reduce((a, b) => a + b) / _guessHistory.length;

  final recentGuesses = _guessHistory.take(7).toList().reversed.toList();
  final accuracyTrend = recentGuesses.map((g) => g.score.toDouble()).toList();

  return {
    'total': _guessHistory.length,
    'avgDeviation': avgDeviation,
    'bestDeviation': bestDeviation,
    'worstDeviation': worstDeviation,
    'avgScore': avgScore,
    'level': SenseLevel.fromDeviation(avgDeviation),
    'accuracyTrend': accuracyTrend,
  };
}

5.4 趋势图表绘制

class TrendChartPainter extends CustomPainter {
  final List<double> data;

  TrendChartPainter(this.data);

  
  void paint(Canvas canvas, Size size) {
    if (data.isEmpty) return;

    final paint = Paint()
      ..color = const Color(0xFF7C4DFF)
      ..strokeWidth = 2
      ..style = PaintingStyle.stroke;

    final fillPaint = Paint()
      ..color = const Color(0xFF7C4DFF).withValues(alpha: 0.1)
      ..style = PaintingStyle.fill;

    final path = Path();
    final fillPath = Path();

    final maxVal = data.reduce((a, b) => a > b ? a : b);
    final minVal = data.reduce((a, b) => a < b ? a : b);
    final range = maxVal - minVal == 0 ? 1.0 : maxVal - minVal;

    final stepX = size.width / (data.length - 1);

    for (int i = 0; i < data.length; i++) {
      final x = i * stepX;
      final y = size.height - ((data[i] - minVal) / range) * size.height * 0.8 - size.height * 0.1;

      if (i == 0) {
        path.moveTo(x, y);
        fillPath.moveTo(x, size.height);
        fillPath.lineTo(x, y);
      } else {
        path.lineTo(x, y);
        fillPath.lineTo(x, y);
      }
    }

    fillPath.lineTo(size.width, size.height);
    fillPath.close();

    canvas.drawPath(fillPath, fillPaint);
    canvas.drawPath(path, paint);

    // 绘制数据点
    final dotPaint = Paint()
      ..color = const Color(0xFF7C4DFF)
      ..style = PaintingStyle.fill;

    for (int i = 0; i < data.length; i++) {
      final x = i * stepX;
      final y = size.height - ((data[i] - minVal) / range) * size.height * 0.8 - size.height * 0.1;
      canvas.drawCircle(Offset(x, y), 4, dotPaint);
    }
  }

  
  bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}

5.5 结果动画实现

Widget _buildResultCard() {
  final guess = _lastGuess!;

  return AnimatedBuilder(
    animation: _resultController,
    builder: (context, child) {
      return Transform.scale(
        scale: 0.8 + _resultController.value * 0.2,
        child: Opacity(
          opacity: _resultController.value,
          child: child,
        ),
      );
    },
    child: Container(
      padding: const EdgeInsets.all(24),
      decoration: BoxDecoration(
        gradient: LinearGradient(
          begin: Alignment.topLeft,
          end: Alignment.bottomRight,
          colors: [
            guess.level.color.withValues(alpha: 0.2),
            guess.level.color.withValues(alpha: 0.05),
          ],
        ),
        borderRadius: BorderRadius.circular(24),
        border: Border.all(
          color: guess.level.color.withValues(alpha: 0.3),
          width: 2,
        ),
      ),
      child: Column(
        children: [
          // 等级展示
          Row(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Text(guess.level.emoji, style: const TextStyle(fontSize: 40)),
              const SizedBox(width: 12),
              Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Text(
                    guess.level.label,
                    style: TextStyle(
                      fontSize: 20,
                      fontWeight: FontWeight.bold,
                      color: guess.level.color,
                    ),
                  ),
                  Row(
                    children: List.generate(
                      5,
                      (i) => Icon(
                        Icons.star,
                        size: 16,
                        color: i < guess.level.stars
                            ? guess.level.color
                            : Colors.grey[300],
                      ),
                    ),
                  ),
                ],
              ),
            ],
          ),
          // 时间对比和偏差信息...
        ],
      ),
    ),
  );
}

六、交互设计

6.1 猜测交互流程

结果卡片 评分系统 确认按钮 滚轮选择器 用户 结果卡片 评分系统 确认按钮 滚轮选择器 用户 滚动选择小时 高亮选中项 滚动选择分钟 启用按钮 点击确认 提交猜测时间 获取实际时间 计算偏差 计算得分 评定等级 返回结果 播放动画 展示结果

6.2 等级提升流程

偏差≤5

偏差≤10

偏差≤20

偏差≤30

偏差>30

用户猜测时间

计算偏差

更新平均偏差

检查等级

时间大师

时间专家

时间达人

时间学徒

时间新手

显示等级进度

6.3 页面切换状态

点击训练Tab

点击历史Tab

点击统计Tab

点击猜时间Tab

点击历史Tab

点击统计Tab

点击猜时间Tab

点击训练Tab

点击统计Tab

点击猜时间Tab

点击训练Tab

点击历史Tab

猜测页

训练页

历史页

统计页


七、扩展功能规划

7.1 后续版本规划

2024-01-07 2024-01-14 2024-01-21 2024-01-28 2024-02-04 2024-02-11 2024-02-18 2024-02-25 2024-03-03 2024-03-10 2024-03-17 时间猜测 评分系统 等级评定 历史记录 统计分析 盲测模式 间隔感知 成就系统 社交挑战 排行榜 个性化训练 V1.0 基础版本 V1.1 增强版本 V1.2 进阶版本 时间感知器开发计划

7.2 功能扩展建议

7.2.1 盲测模式

增强训练难度:

  • 隐藏所有时间信息
  • 随机时间段猜测
  • 夜间模式挑战
7.2.2 间隔感知

感知时间流逝:

  • 估计5分钟有多长
  • 估计1小时有多长
  • 间隔感知训练
7.2.3 成就系统

激励持续练习:

  • 连续猜测成就
  • 精准猜测成就
  • 等级提升成就

八、注意事项

8.1 开发注意事项

  1. 时间获取:使用DateTime.now()获取当前时间,确保准确性

  2. 动画控制:三个AnimationController需要在dispose时释放

  3. 状态管理:使用setState管理本地状态,注意_hasGuessed状态切换

  4. 滚轮选择器:使用ListWheelScrollView实现,注意physics设置

8.2 常见问题

问题 原因 解决方案
滚轮不滚动 physics未设置 使用FixedExtentScrollPhysics
结果不显示 _hasGuessed未设置 检查submitGuess方法
等级计算错误 偏差计算问题 检查分钟数转换
图表不显示 数据为空 检查accuracyTrend

8.3 使用提示

⏰ 时间感知器使用小贴士 ⏰

不看表,凭直觉猜时间。
持续练习,提升时间感。
每天坚持,成为时间大师。
记住,时间感知是可以训练的!


九、运行说明

9.1 环境要求

环境 版本要求
Flutter SDK >= 3.0.0
Dart SDK >= 2.17.0
鸿蒙OS API 21+

9.2 运行命令

# 查看可用设备
flutter devices

# 运行到Web服务器
flutter run -d web-server -t lib/main_time_sense.dart --web-port 8122

# 运行到鸿蒙设备
flutter run -d 127.0.0.1:5555 lib/main_time_sense.dart

# 运行到Windows
flutter run -d windows -t lib/main_time_sense.dart

# 代码分析
flutter analyze lib/main_time_sense.dart

十、总结

时间感知器是一款独特的时间感知训练工具,通过游戏化的猜测机制帮助用户培养"不看表就能感知时间"的能力。应用内置五级等级体系,从"时间新手"到"时间大师",激励用户持续练习提升。

核心功能涵盖时间猜测、评分系统、等级评定、训练指导、历史记录、统计分析六大模块。时间猜测采用滚轮选择器,操作直观流畅;评分系统根据偏差计算得分,公平客观;等级评定提供明确的成长目标;训练指导帮助用户掌握训练技巧;历史记录回顾每次猜测;统计分析展示准确度趋势。

应用采用Material Design 3设计规范,以深邃的紫色为主色调,营造神秘专注的氛围。通过本应用,希望能够帮助用户重新找回对时间的敏感度,成为真正的时间感知大师。

培养时间感知,掌控生活节奏

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐