Flutter for OpenHarmony 视力保护提醒App实战 - 快速护眼锻炼
本文介绍了快速护眼锻炼页面的设计与实现,包含锻炼方法选择器、详情展示和开始按钮三个核心部分。使用Flutter框架开发,采用StatefulWidget管理用户选择的锻炼方法状态。页面提供四种锻炼方式(眼球转动、远近调焦、眼睛放松和眼部按摩),每种方法配有标题、描述、耗时和图标。水平滚动的选择器通过颜色变化提供视觉反馈,详情区域展示选中方法的完整信息。这种模块化设计提升了代码可维护性,同时为用户提

快速护眼锻炼是应用的重要功能,它提供了多种眼睛锻炼方法来帮助用户缓解眼睛疲劳。本文将详细讲解如何实现一个快速护眼锻炼页面,包括锻炼方法的选择、详细说明和开始锻炼等功能。
快速护眼锻炼页面的设计
快速护眼锻炼页面包含锻炼方法选择器、锻炼详情展示和开始锻炼按钮等多个部分。这样的设计可以让用户方便地选择和进行眼睛锻炼。
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
class QuickExerciseDetail extends StatefulWidget {
const QuickExerciseDetail({super.key});
State<QuickExerciseDetail> createState() => _QuickExerciseDetailState();
}
class _QuickExerciseDetailState extends State<QuickExerciseDetail> {
int _selectedExercise = 0;
final exercises = [
{
'title': '眼球转动',
'description': '缓慢转动眼球,顺时针和逆时针各10次',
'duration': '2分钟',
'icon': Icons.rotate_right,
},
{
'title': '远近调焦',
'description': '看远处30秒,再看近处30秒,重复5次',
'duration': '3分钟',
'icon': Icons.visibility,
},
{
'title': '眼睛放松',
'description': '闭眼休息,深呼吸,放松眼部肌肉',
'duration': '5分钟',
'icon': Icons.spa,
},
{
'title': '眼部按摩',
'description': '轻轻按摩眼周穴位,促进血液循环',
'duration': '3分钟',
'icon': Icons.touch_app,
},
];
这段代码定义了QuickExerciseDetail有状态组件和四种不同的眼睛锻炼方法。每种锻炼方法都包含标题、描述、耗时和对应的图标,这样的数据结构便于后续的UI展示和用户交互。通过使用StatefulWidget,我们可以管理用户选择的锻炼方法状态,实现动态的UI更新。这种设计模式遵循了Flutter的最佳实践,将数据和UI逻辑分离,提高了代码的可维护性和可读性。
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('快速锻炼'),
backgroundColor: const Color(0xFFE91E63),
foregroundColor: Colors.white,
),
body: SingleChildScrollView(
child: Column(
children: [
_buildExerciseSelector(),
SizedBox(height: 16.h),
_buildExerciseDetail(),
SizedBox(height: 16.h),
_buildStartButton(),
SizedBox(height: 20.h),
],
),
),
);
}
build方法构建了整个页面的布局结构。使用Scaffold作为基础框架,提供了标准的Material Design布局。AppBar显示页面标题和粉红色的主题色,SingleChildScrollView包装了主体内容,确保当内容超过屏幕高度时可以滚动。通过将页面分解为三个主要部分(选择器、详情、按钮),代码结构清晰,易于维护和扩展。
快速护眼锻炼页面使用StatefulWidget来管理选中的锻炼方法。我们定义了四种不同的锻炼方法,每种方法都有自己的标题、描述、耗时和图标。
锻炼方法选择器的实现
锻炼方法选择器允许用户在不同的锻炼方法之间切换。
Widget _buildExerciseSelector() {
return SizedBox(
height: 100.h,
child: ListView.builder(
scrollDirection: Axis.horizontal,
padding: EdgeInsets.symmetric(horizontal: 16.w),
itemCount: exercises.length,
itemBuilder: (context, index) {
final isSelected = _selectedExercise == index;
return GestureDetector(
onTap: () => setState(() => _selectedExercise = index),
child: Container(
width: 80.w,
margin: EdgeInsets.only(right: 12.w),
decoration: BoxDecoration(
color: isSelected ? const Color(0xFFE91E63) : Colors.white,
borderRadius: BorderRadius.circular(12.r),
border: Border.all(
color: isSelected ? const Color(0xFFE91E63) : Colors.grey[300]!,
width: 2,
),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
exercises[index]['icon'] as IconData,
color: isSelected ? Colors.white : const Color(0xFFE91E63),
size: 24.sp,
),
SizedBox(height: 4.h),
Text(
exercises[index]['title'] as String,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 10.sp,
color: isSelected ? Colors.white : Colors.black,
fontWeight: FontWeight.w500,
),
),
],
),
),
);
},
),
);
}
代码说明: _buildExerciseSelector()方法使用ListView.builder创建水平滚动的锻炼方法选择器。通过GestureDetector捕获用户的点击事件,调用setState()更新选中的锻炼索引。每个卡片根据isSelected状态动态改变背景色、图标颜色和文字颜色,选中时为粉红色,未选中时为白色。SizedBox设置固定高度为100.h,确保选择器的尺寸一致。ListView.builder的scrollDirection: Axis.horizontal实现了水平滚动效果。
选择器的交互设计: 这个选择器采用了状态驱动的UI设计模式。通过_selectedExercise状态变量管理选中项,确保UI与数据状态同步。使用ListView.builder实现高效的列表渲染,只构建可见的卡片。水平滚动的设计使得用户可以轻松浏览所有锻炼方法。颜色变化提供了清晰的视觉反馈,帮助用户快速识别当前选中的锻炼。这种设计提供了流畅的用户交互体验,同时保持了代码的可维护性。
锻炼详情的展示
锻炼详情部分显示选中锻炼方法的详细信息。
Widget _buildExerciseDetail() {
final exercise = exercises[_selectedExercise];
return Container(
margin: EdgeInsets.symmetric(horizontal: 16.w),
padding: EdgeInsets.all(20.w),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12.r),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 8,
offset: const Offset(0, 2),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(
exercise['icon'] as IconData,
color: const Color(0xFFE91E63),
size: 32.sp,
),
SizedBox(width: 12.w),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
exercise['title'] as String,
style: TextStyle(
fontSize: 18.sp,
fontWeight: FontWeight.bold,
),
),
Text(
'耗时: ${exercise['duration']}',
style: TextStyle(
fontSize: 12.sp,
color: Colors.grey,
),
),
],
),
),
],
),
SizedBox(height: 16.h),
Text(
'说明',
style: TextStyle(fontSize: 14.sp, fontWeight: FontWeight.bold),
),
SizedBox(height: 8.h),
Text(
exercise['description'] as String,
style: TextStyle(fontSize: 14.sp, color: Colors.grey[700]),
),
],
),
);
}
代码说明: _buildExerciseDetail()方法构建锻炼详情卡片,显示选中锻炼的完整信息。使用Row和Expanded布局图标、标题和耗时,确保响应式设计。下方显示详细的锻炼说明文本。通过BoxShadow添加阴影效果,提升视觉层次感。Container使用EdgeInsets.symmetric设置对称的外边距和内边距,确保卡片与屏幕边缘的距离一致。
详情卡片的设计优势: 这个详情卡片采用了信息分层展示的设计模式。上部分为快速信息(图标、标题、耗时),下部分为详细说明,符合用户的阅读习惯。使用Container和BoxDecoration创建统一的卡片风格,与整个应用的设计语言保持一致。粉红色的图标与应用主题色相呼应,提高了品牌识别度。这种设计提高了信息的可读性和用户体验。
开始锻炼按钮的实现
开始锻炼按钮允许用户开始进行选中的锻炼。
Widget _buildStartButton() {
return Padding(
padding: EdgeInsets.symmetric(horizontal: 16.w),
child: SizedBox(
width: double.infinity,
child: ElevatedButton.icon(
onPressed: () {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('开始${exercises[_selectedExercise]['title']}'),
backgroundColor: const Color(0xFFE91E63),
),
);
},
icon: const Icon(Icons.play_arrow),
label: const Text('开始锻炼'),
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFFE91E63),
foregroundColor: Colors.white,
padding: EdgeInsets.symmetric(vertical: 12.h),
),
),
),
);
}
}
代码说明: _buildStartButton()方法创建一个全宽的粉红色按钮,使用ElevatedButton.icon显示播放图标和"开始锻炼"文本。点击时通过ScaffoldMessenger.showSnackBar()显示提示信息,告知用户已开始的锻炼名称。按钮使用SizedBox和Padding实现响应式布局。ElevatedButton.styleFrom设置按钮的背景色、前景色和内边距。double.infinity使按钮宽度填满父容器。
按钮交互设计: 这个按钮采用了反馈驱动的交互设计。通过SnackBar提供即时的用户反馈,确认用户的操作已被系统接收。按钮的粉红色主题色与整个应用保持一致,提高了品牌识别度。全宽设计使得按钮易于点击,提升了移动应用的可用性。播放图标的使用直观地表达了"开始"的含义,降低了用户的理解成本。
锻炼计时器
锻炼计时器是快速护眼锻炼的重要组件,用于帮助用户按时完成锻炼。
class ExerciseTimer {
int _remainingSeconds = 0;
bool _isRunning = false;
late Timer _timer;
final Function(int) onTick;
final Function() onComplete;
ExerciseTimer({
required this.onTick,
required this.onComplete,
});
void startTimer(int durationInSeconds) {
_remainingSeconds = durationInSeconds;
_isRunning = true;
_timer = Timer.periodic(const Duration(seconds: 1), (timer) {
_remainingSeconds--;
onTick(_remainingSeconds);
if (_remainingSeconds <= 0) {
stopTimer();
onComplete();
}
});
}
void pauseTimer() {
if (_isRunning) {
_timer.cancel();
_isRunning = false;
}
}
void resumeTimer() {
if (!_isRunning && _remainingSeconds > 0) {
_isRunning = true;
_timer = Timer.periodic(const Duration(seconds: 1), (timer) {
_remainingSeconds--;
onTick(_remainingSeconds);
if (_remainingSeconds <= 0) {
stopTimer();
onComplete();
}
});
}
}
void stopTimer() {
if (_isRunning) {
_timer.cancel();
_isRunning = false;
_remainingSeconds = 0;
}
}
int getRemainingSeconds() => _remainingSeconds;
bool isRunning() => _isRunning;
}
代码说明: 这部分代码定义了ExerciseTimer类,用于管理锻炼的计时功能。_remainingSeconds存储剩余的秒数,_isRunning标记计时器是否正在运行。onTick和onComplete是回调函数,分别在每秒更新和计时完成时调用。startTimer()方法启动计时器,使用Timer.periodic()每秒触发一次回调。pauseTimer()方法暂停计时器,resumeTimer()方法恢复计时。stopTimer()方法停止计时器并重置状态。getRemainingSeconds()和isRunning()是getter方法,用于获取计时器的当前状态。
计时器的核心设计: 这个计时器采用了回调模式,通过onTick和onComplete回调函数与UI层解耦。Timer.periodic()提供了精确的定时功能,每秒触发一次。暂停和恢复功能通过取消和重新创建定时器实现,确保了计时的准确性。这种设计使得计时器可以独立于UI存在,便于单元测试和代码复用。
锻炼记录管理
锻炼记录管理用于跟踪用户的锻炼历史和统计数据。
class ExerciseRecord {
final String id;
final String exerciseTitle;
final DateTime timestamp;
final int durationInSeconds;
final bool completed;
final String notes;
ExerciseRecord({
required this.id,
required this.exerciseTitle,
required this.timestamp,
required this.durationInSeconds,
required this.completed,
required this.notes,
});
Map<String, dynamic> toJson() {
return {
'id': id,
'exerciseTitle': exerciseTitle,
'timestamp': timestamp.toIso8601String(),
'durationInSeconds': durationInSeconds,
'completed': completed,
'notes': notes,
};
}
factory ExerciseRecord.fromJson(Map<String, dynamic> json) {
return ExerciseRecord(
id: json['id'],
exerciseTitle: json['exerciseTitle'],
timestamp: DateTime.parse(json['timestamp']),
durationInSeconds: json['durationInSeconds'],
completed: json['completed'],
notes: json['notes'],
);
}
}
代码说明: 这部分代码定义了ExerciseRecord类,用于表示单条锻炼记录。包含id(唯一标识符)、exerciseTitle(锻炼名称)、timestamp(锻炼时间)、durationInSeconds(锻炼时长)、completed(是否完成)和notes(备注)。toJson()方法将对象转换为JSON格式,便于存储。fromJson()工厂构造函数从JSON数据重建对象。这种设计支持数据的序列化和反序列化,便于持久化存储。DateTime.parse()和toIso8601String()确保了时间数据的正确处理。
数据模型设计: 这个模型包含了锻炼记录的所有关键信息,支持完整的生命周期管理。通过completed字段可以区分已完成和未完成的锻炼。notes字段允许用户添加个人备注,增加了应用的灵活性。toJson()和fromJson()方法遵循了Dart的标准序列化模式,便于与数据库或网络API集成。
class ExerciseRecordManager {
static final ExerciseRecordManager _instance = ExerciseRecordManager._internal();
final List<ExerciseRecord> _records = [];
factory ExerciseRecordManager() {
return _instance;
}
ExerciseRecordManager._internal();
void addRecord(ExerciseRecord record) {
_records.add(record);
}
List<ExerciseRecord> getRecordsByDate(DateTime date) {
return _records
.where((record) =>
record.timestamp.year == date.year &&
record.timestamp.month == date.month &&
record.timestamp.day == date.day)
.toList();
}
int getCompletedCountByDate(DateTime date) {
return getRecordsByDate(date).where((record) => record.completed).length;
}
int getTotalDurationByDate(DateTime date) {
return getRecordsByDate(date)
.fold<int>(0, (prev, record) => prev + record.durationInSeconds);
}
List<ExerciseRecord> getRecordsByExercise(String exerciseTitle) {
return _records.where((record) => record.exerciseTitle == exerciseTitle).toList();
}
}
代码说明: ExerciseRecordManager类采用单例模式管理所有的锻炼记录。_records列表存储所有的锻炼记录。addRecord()方法添加新的锻炼记录。getRecordsByDate()方法根据指定日期获取该天的所有锻炼记录,通过比较年月日来精确匹配。getCompletedCountByDate()方法计算指定日期完成的锻炼次数。getTotalDurationByDate()方法计算指定日期的总锻炼时长,使用fold()函数进行求和。getRecordsByExercise()方法根据锻炼名称获取相关的所有记录。
数据管理设计: 单例模式确保了全局数据的一致性和访问的便利性。使用where()和fold()等函数式编程方法进行数据过滤和聚合,代码简洁高效。按日期和锻炼类型的查询方法支持多维度的数据分析,使得应用可以轻松生成各种统计报告。这种设计提供了灵活的数据查询接口和良好的可扩展性。
锻炼效果评估
锻炼效果评估用于分析用户的锻炼成果和改进建议。
class ExerciseEffectAnalyzer {
static Map<String, dynamic> analyzeWeeklyProgress(
List<ExerciseRecord> records,
) {
if (records.isEmpty) {
return {
'totalSessions': 0,
'completionRate': 0.0,
'totalDuration': 0,
'averageDuration': 0,
'trend': '无数据',
};
}
final completedCount = records.where((r) => r.completed).length;
final completionRate = (completedCount / records.length) * 100;
final totalDuration = records.fold<int>(0, (prev, r) => prev + r.durationInSeconds);
final averageDuration = totalDuration ~/ records.length;
return {
'totalSessions': records.length,
'completionRate': completionRate,
'totalDuration': totalDuration,
'averageDuration': averageDuration,
'trend': completionRate >= 80 ? '优秀' : completionRate >= 60 ? '良好' : '需要改善',
};
}
static String getRecommendation(Map<String, dynamic> analysis) {
final completionRate = analysis['completionRate'] as double;
if (completionRate >= 80) {
return '坚持保持,你的锻炼效果很好!';
} else if (completionRate >= 60) {
return '继续努力,增加锻炼频率会更有效果。';
} else {
return '建议增加锻炼次数,每天至少进行一次护眼锻炼。';
}
}
static Color getTrendColor(String trend) {
switch (trend) {
case '优秀':
return const Color(0xFF4CAF50);
case '良好':
return const Color(0xFF2196F3);
default:
return const Color(0xFFFFC107);
}
}
}
代码说明: ExerciseEffectAnalyzer类提供了静态方法用于分析锻炼效果。analyzeWeeklyProgress()方法计算总锻炼次数、完成率、总时长和平均时长。完成率通过已完成的记录数除以总记录数计算。根据完成率判断锻炼趋势(优秀/良好/需要改善)。getRecommendation()方法根据分析结果提供个性化的建议。getTrendColor()方法根据趋势返回对应的颜色。空列表时返回默认的零值数据。
分析设计模式: 这个分析器采用了多维度评估方法,通过完成率、总时长和平均时长等指标全面评估锻炼效果。三级评分系统提供了清晰的反馈。个性化建议根据用户的实际情况提供针对性的指导。颜色映射使得用户可以直观地理解评估结果。静态方法设计使得分析功能可以独立使用,便于测试和复用。
总结
通过以上的设计,我们实现了一个完整的快速护眼锻炼系统。包括锻炼计时器、锻炼记录管理和锻炼效果评估。用户可以进行定时的眼睛锻炼、查看锻炼历史、获得效果评估和改进建议。这样的设计使得用户能够系统地进行护眼锻炼,并了解锻炼的效果。
在实际应用中,我们可以进一步扩展这个系统,添加更多的锻炼方法、社交分享功能、排行榜等功能。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐


所有评论(0)