flutter_for_openharmony口腔护理app实战+刷牙计时实现

刷牙计时是口腔护理App的核心功能之一,帮助用户养成科学的刷牙习惯。牙医建议每次刷牙至少2-3分钟,但很多人实际刷牙时间远远不够。通过计时功能,用户可以直观地看到自己的刷牙时长,逐步养成正确的刷牙习惯。
计时功能的设计目标
计时页面需要实现几个核心功能:显示当前刷牙时长、提供开始/暂停/重置控制、达到目标时间后提醒用户、保存刷牙记录并计算评分。界面设计要简洁直观,用户在刷牙时能够一眼看清当前进度,操作按钮要足够大方便单手操作。
依赖导入
import 'dart:async';
dart:async库提供了Timer类,用于实现定时器功能。
这是实现计时功能的核心依赖。
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
Flutter核心库和Provider状态管理。
Provider用于保存刷牙记录到全局状态。
import 'package:percent_indicator/circular_percent_indicator.dart';
环形进度指示器组件。
用于显示刷牙进度,比纯文字更直观。
import '../../providers/app_provider.dart';
import '../../models/oral_models.dart';
AppProvider用于保存记录,BrushRecord是刷牙记录的数据模型。
状态变量定义
class BrushTimerPage extends StatefulWidget {
const BrushTimerPage({super.key});
State<BrushTimerPage> createState() => _BrushTimerPageState();
}
计时页面需要管理多个状态,所以使用StatefulWidget。
包括计时秒数、运行状态、选中的时间段等。
class _BrushTimerPageState extends State<BrushTimerPage> {
int _seconds = 0;
int _targetSeconds = 180;
_seconds记录当前已刷牙的秒数。
_targetSeconds是目标时长,默认180秒即3分钟。
Timer? _timer;
bool _isRunning = false;
_timer是定时器对象,用于每秒更新计时。
_isRunning标记计时器是否正在运行。
String _selectedType = 'morning';
final Map<String, String> _typeLabels = {
'morning': '早晨',
'noon': '中午',
'evening': '晚上',
};
_selectedType记录用户选择的刷牙时间段。
_typeLabels定义时间段的显示文字。
生命周期管理
void dispose() {
_timer?.cancel();
super.dispose();
}
dispose方法在页面销毁时调用。
必须取消定时器,否则会造成内存泄漏和后台持续运行。
计时器控制方法
void _startTimer() {
setState(() => _isRunning = true);
_timer = Timer.periodic(const Duration(seconds: 1), (timer) {
setState(() {
_seconds++;
if (_seconds >= _targetSeconds) {
_stopTimer();
_showCompleteDialog();
}
});
});
}
_startTimer启动计时器。
Timer.periodic每秒执行一次回调,更新_seconds并检查是否达到目标。
void _stopTimer() {
_timer?.cancel();
setState(() => _isRunning = false);
}
_stopTimer暂停计时器。
调用cancel()停止定时器,更新运行状态。
void _resetTimer() {
_timer?.cancel();
setState(() {
_seconds = 0;
_isRunning = false;
});
}
_resetTimer重置计时器。
停止定时器并将秒数归零。
保存记录功能
void _saveRecord() {
if (_seconds < 30) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('刷牙时间太短,至少需要30秒')),
);
return;
}
保存前检查刷牙时长。
少于30秒不允许保存,用SnackBar提示用户。
final score = _calculateScore();
final record = BrushRecord(
dateTime: DateTime.now(),
durationSeconds: _seconds,
type: _selectedType,
score: score,
);
计算评分并创建BrushRecord对象。
记录包含时间、时长、类型和评分四个字段。
context.read<AppProvider>().addBrushRecord(record);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('记录已保存!评分:$score')),
);
_resetTimer();
}
调用provider的方法保存记录。
显示保存成功提示,然后重置计时器。
评分计算逻辑
int _calculateScore() {
if (_seconds >= 180) return 100;
if (_seconds >= 150) return 95;
if (_seconds >= 120) return 90;
if (_seconds >= 90) return 85;
if (_seconds >= 60) return 80;
return 70;
}
根据刷牙时长计算评分。
3分钟以上满分,时间越短分数越低。
这种阶梯式评分鼓励用户延长刷牙时间。
完成提醒对话框
void _showCompleteDialog() {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('🎉 刷牙完成!'),
content: const Text('太棒了!你已经完成了3分钟的刷牙,保持良好习惯!'),
达到目标时间后弹出对话框。
标题带emoji增加趣味性,内容给予正向鼓励。
actions: [
TextButton(
onPressed: () {
Navigator.pop(context);
_saveRecord();
},
child: const Text('保存记录'),
),
],
),
);
}
对话框只有一个"保存记录"按钮。
点击后关闭对话框并保存记录。
页面UI构建
Widget build(BuildContext context) {
final progress = _seconds / _targetSeconds;
final minutes = _seconds ~/ 60;
final secs = _seconds % 60;
计算进度百分比和分秒显示值。
~/是整除运算符,%是取余运算符。
return Scaffold(
appBar: AppBar(title: const Text('刷牙计时')),
body: SingleChildScrollView(
padding: const EdgeInsets.all(24),
Scaffold提供页面结构,SingleChildScrollView让内容可滚动。
24像素内边距让内容不会太靠边。
时间段选择器
Container(
padding: const EdgeInsets.all(4),
decoration: BoxDecoration(
color: Colors.grey.shade200,
borderRadius: BorderRadius.circular(12),
),
时间段选择器的外层容器。
灰色背景,圆角设计。
child: Row(
children: _typeLabels.entries.map((entry) => Expanded(
child: GestureDetector(
onTap: () => setState(() => _selectedType = entry.key),
三个选项横向排列,Expanded让它们等宽。
点击时更新选中状态。
child: Container(
padding: const EdgeInsets.symmetric(vertical: 12),
decoration: BoxDecoration(
color: _selectedType == entry.key ? const Color(0xFF26A69A) : Colors.transparent,
borderRadius: BorderRadius.circular(10),
),
选中的选项显示绿色背景,未选中透明。
这种设计类似iOS的分段控制器。
child: Text(
entry.value,
textAlign: TextAlign.center,
style: TextStyle(
color: _selectedType == entry.key ? Colors.white : Colors.grey.shade600,
fontWeight: FontWeight.bold,
),
),
),
),
)).toList(),
),
),
选中时文字白色,未选中时灰色。
加粗字体让文字更清晰。
环形进度指示器
CircularPercentIndicator(
radius: 140,
lineWidth: 15,
percent: progress.clamp(0, 1),
大尺寸的环形进度条,半径140像素。
线宽15像素,进度值限制在0-1之间。
center: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'${minutes.toString().padLeft(2, '0')}:${secs.toString().padLeft(2, '0')}',
style: const TextStyle(fontSize: 48, fontWeight: FontWeight.bold),
),
环形中间显示时间,格式为"00:00"。
padLeft确保个位数时前面补0。
Text(
'目标 ${_targetSeconds ~/ 60} 分钟',
style: TextStyle(color: Colors.grey.shade600),
),
],
),
下方显示目标时长提示。
灰色小字作为辅助信息。
progressColor: const Color(0xFF26A69A),
backgroundColor: Colors.grey.shade200,
circularStrokeCap: CircularStrokeCap.round,
),
进度条绿色,背景灰色,两端圆形。
控制按钮区域
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (!_isRunning && _seconds > 0)
ElevatedButton.icon(
onPressed: _resetTimer,
icon: const Icon(Icons.refresh),
label: const Text('重置'),
重置按钮只在暂停且有计时时显示。
使用条件渲染控制按钮的显示。
style: ElevatedButton.styleFrom(
backgroundColor: Colors.grey.shade300,
foregroundColor: Colors.black87,
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
),
),
灰色背景,黑色文字,与主按钮区分。
ElevatedButton.icon(
onPressed: _isRunning ? _stopTimer : _startTimer,
icon: Icon(_isRunning ? Icons.pause : Icons.play_arrow),
label: Text(_isRunning ? '暂停' : (_seconds > 0 ? '继续' : '开始')),
主按钮根据状态显示不同的图标和文字。
运行中显示暂停,暂停时显示继续或开始。
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF26A69A),
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 12),
),
),
],
),
绿色背景,白色文字,是页面的主要操作按钮。
保存记录按钮
if (_seconds > 0 && !_isRunning)
ElevatedButton.icon(
onPressed: _saveRecord,
icon: const Icon(Icons.save),
label: const Text('保存记录'),
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF4DB6AC),
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 12),
),
),
保存按钮只在暂停且有计时时显示。
使用稍浅的绿色与主按钮区分。
刷牙提示区域
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: const Color(0xFF26A69A).withOpacity(0.1),
borderRadius: BorderRadius.circular(12),
),
提示区域使用浅绿色背景。
圆角设计与整体风格一致。
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: const [
Text('刷牙小贴士', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16)),
SizedBox(height: 8),
Text('• 使用巴氏刷牙法,牙刷与牙齿呈45度角'),
Text('• 每个区域刷10-15次'),
Text('• 不要忘记刷舌头'),
Text('• 建议每次刷牙2-3分钟'),
],
),
),
],
),
),
);
}
}
提供实用的刷牙建议。
用户在等待计时的同时可以学习正确的刷牙方法。
Timer的使用注意事项
Timer.periodic创建的定时器会持续运行,即使页面不可见。因此必须在dispose方法中取消定时器,否则会造成内存泄漏。另外,定时器的回调中调用setState时,如果Widget已经被销毁会报错,所以在复杂场景下需要先检查mounted属性。
小结
刷牙计时页面通过Timer实现了精确的秒级计时,环形进度条直观展示刷牙进度。时间段选择器让用户标记是早中晚哪次刷牙,评分系统根据时长给出分数激励用户。控制按钮根据状态动态显示,交互逻辑清晰。刷牙提示区域提供了实用的护理建议,让页面不仅是工具,也是知识传播的载体。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)