AtomGit Flutter 鸿蒙客户端:倒计时与触觉反馈
2026/7/9 18:12:00 网站建设 项目流程

倒计时不只是数字递减——配合触觉反馈,可以为放松体验增加一层"物理感",让用户即使闭着眼睛也能感知时间的流动。


一、设计理念:让用户不必看屏幕

白噪音模块的核心使用场景是:用户闭上眼睛,沉浸在放松状态中。在这种场景下,屏幕上的视觉信息对用户是"不可见"的。

传统的解决方式是音频提示("叮"的一声),但 E-Brufen 当前没有音频播放能力。于是我们转向了触觉通道

视觉通道(眼睛闭着) ↓ 不可用 听觉通道(无音频播放) ↓ 不可用 触觉通道 ← E-Brufen 的主反馈通道! ↓ 用户感知到震动 → 知道时间在流逝 → 保持放松状态

二、定时器选项设计

2.1 预设时长

finalList<int>_durations=[15,30,60,90];Wrap(spacing:8,children:[..._durations.map((d)=>ChoiceChip(label:Text('$d分'),selected:_remainingSec>0&&_remainingSec~/60==d,onSelected:(_)=>setState((){_remainingSec=d*60;widget.settings.timerDuration=d;// 持久化偏好}),)),ChoiceChip(label:constText('不限'),selected:_remainingSec==0,onSelected:(_)=>setState(()=>_remainingSec=0),),])

设计考量

时长适用场景触觉反馈次数
15 分钟快速放松、课间休息14 次轻触 + 1 次重触
30 分钟午休小憩29 次轻触 + 1 次重触
60 分钟深度放松59 次轻触 + 1 次重触
90 分钟完整睡眠周期89 次轻触 + 1 次重触
不限背景白噪音(办公等)无(不启动 Timer)

⚠️ "不限"选项不启动 Timer,_remainingSec == 0作为特殊标记。此时_startCountdown()直接 return,不会消耗系统资源。


三、Timer.periodic 倒计时实现

3.1 核心代码

void_startCountdown(){_countdown?.cancel();// 取消旧的 Timer(防重复)if(_remainingSec<=0)return;// "不限"模式直接返回_countdown=Timer.periodic(constDuration(seconds:1),(timer){if(!_isPlaying){// 手动停止 → 取消计时timer.cancel();return;}setState((){_remainingSec--;if(_remainingSec<=0){// 时间到_onTimerDone();timer.cancel();return;}// ⏰ 每分钟一次轻触觉——"无声的报时"if(_remainingSec%60==0){HapticFeedback.lightImpact();}});});}

3.2 状态流程图

用户点击播放 ↓ _startCountdown() → Timer.periodic(每秒) ↓ 每秒回调: ├─ !_isPlaying? → timer.cancel() (用户手动停止) ├─ _remainingSec == 0? → _onTimerDone() (自然结束) ├─ _remainingSec % 60 == 0? → lightImpact() (分钟标记) └─ _remainingSec-- (继续倒计时)

★ Insight ─────────────────────────────────────
_remainingSec % 60 == 0是"无声提醒"的核心。用户闭眼放松时,每分钟一次轻触觉让他知道"又过了一分钟",而不需要睁眼看屏幕。这是 E-Brufen 最精妙的设计细节之一——它把"必须看"变成了"可以感受"。
─────────────────────────────────────────────────


四、完成时的双重反馈

void_onTimerDone(){setState((){_isPlaying=false;});_pulse.stop();_pulse.reset();HapticFeedback.heavyImpact();// 强烈的"唤醒"震动if(mounted){ScaffoldMessenger.of(context).showSnackBar(constSnackBar(content:Text('时间到,放松结束 🌙'),duration:Duration(seconds:2),),);}}

双重信号确保用户一定感知到结束:

  1. heavyImpact()— 强烈的触觉(即使在口袋中也能感知)
  2. SnackBar— 视觉确认(如果屏幕可见)

⚠️if (mounted)检查是必须的——Timer 回调可能在 Widget 已销毁后触发(如用户快速返回首页),不检查会导致setState() called after dispose()崩溃。


五、HapticFeedback 类型详解

Flutter 提供四种触觉反馈强度:

类型强度持续时间E-Brufen 中的使用
lightImpact()~10ms✅ 每分钟标记
mediumImpact()~15ms可用于场景切换确认
heavyImpact()~20ms✅ 定时结束唤醒
selectionClick()极轻~5ms可用于 emoji 选择反馈

选择lightImpact而非selectionClick的原因selectionClick太轻,在口袋或桌面上可能感知不到。lightImpact提供了足够的感知强度而不至于"惊醒"放松中的用户。


六、播放/停止的对称控制

void_togglePlay(){setState((){_isPlaying=!_isPlaying;if(_isPlaying){_pulse.repeat(reverse:true);// 启动脉冲动画_startCountdown();// 启动倒计时}else{_pulse.stop();// 停止动画_pulse.reset();_countdown?.cancel();// 取消倒计时}});}

一对对称操作:播放时同时启动动画和 Timer,停止时同时取消两者。这确保了动画和计时永远同步。


七、资源释放

@overridevoiddispose(){_pulse.dispose();// 释放 AnimationController_countdown?.cancel();// 取消 Timer(防止内存泄漏)super.dispose();}

释放顺序:先dispose动画(可能有回调引用 Timer),再cancelTimer。两个异步资源都要清理——漏掉任何一个都会导致内存泄漏。


八、时间格式化

String_formatTime(int s){finalm=s~/60;finalsec=s%60;return'${m.toString().padLeft(2, '0')}:${sec.toString().padLeft(2, '0')}';}// 03:45, 00:30, 01:00

上一篇:白噪音场景的视觉设计
下一篇:ChangeNotifier 模式:轻量级状态管理


作者简介:E-Brufen Dev,Flutter & 鸿蒙开发者。触觉反馈设计基于 HapticFeedback API 在鸿蒙 6.0 真机上的实测体验,项目地址:AtomGit - E-Brufen。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询