news 2026/6/9 23:40:24

Flutter for OpenHarmony 打造沉浸式呼吸引导应用:用动画疗愈身心

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Flutter for OpenHarmony 打造沉浸式呼吸引导应用:用动画疗愈身心

Flutter for OpenHarmony 打造沉浸式呼吸引导应用:用动画疗愈身心

在快节奏的现代生活中,呼吸——这一最自然却常被忽视的生命节律——正成为连接身心、缓解焦虑的关键工具。科学研究表明,有意识的深呼吸练习能有效降低心率、减轻压力、提升专注力。然而,许多人虽知其益,却苦于缺乏引导而难以坚持。

🌐 加入社区 欢迎加入开源鸿蒙跨平台开发者社区,获取最新资源与技术支持: 👉开源鸿蒙跨平台开发者社区


完整效果


一、设计理念:让呼吸“可见”

该应用的核心思想是“可视化呼吸”

💡目标:用户无需思考“现在该做什么”,只需跟随视觉引导,自然进入呼吸节奏。


二、呼吸训练模型:4-7-8 的变体

虽然代码中未显式写出各阶段时长,但从AnimationController(duration: const Duration(seconds: 24))和四阶段均分可推断,每阶段约6 秒,形成一个6-6-6-6的对称循环:

  1. 吸气(Inhale):6 秒,缓慢深吸;
  2. 屏息(Hold):6 秒,保持气息;
  3. 呼气(Exhale):6 秒,缓慢深呼;
  4. 空息(Hold):6 秒,保持空腔。

🌿 这种对称设计简化了认知负担,适合初学者建立呼吸节奏感。


三、核心技术实现

1. 动画驱动:AnimationController+CurvedAnimation

_animationController=AnimationController(duration:constDuration(seconds:24),// 一个完整循环24秒vsync:this,);_breathAnimation=Tween<double>(begin:0.3,end:1.0).animate(CurvedAnimation(parent:_animationController,curve:Curves.easeInOut),);

2. 阶段识别:从连续动画到离散状态

_breathAnimation.addListener((){setState((){_currentPhase=(_animationController.value*4).floor();if(_currentPhase>=4)_currentPhase=3;});});

3. 循环控制:自动重置与计数

..addStatusListener((status){if(status==AnimationStatus.completed){setState((){_cycleCount++;_currentPhase=0;});_animationController.reset();if(_isRunning)_animationController.forward();// 自动开始下一循环}});

四、UI/UX 设计亮点

1. 色彩心理学应用

阶段颜色心理暗示
吸气🟢 绿色 (green.shade400)生长、能量、吸入生命力
屏息(吸后)🟡 琥珀色 (amber.shade300)温暖、稳定、蓄势待发
呼气🔴 红色 (red.shade400)释放、排出、代谢废物
屏息(呼后)🔵 蓝色 (blue.shade300)冷静、空灵、内在平静

每种颜色不仅用于中心球,还同步应用于:

2. 多层次视觉反馈

3. 交互设计


五、代码结构与健壮性


六、应用场景与扩展可能

适用场景

可扩展方向


七、结语:技术为身心服务

这段代码远不止是一个动画演示,它体现了“科技向善”的理念——用精巧的技术手段,服务于最基础的人类需求:呼吸

完整代码

import'package:flutter/material.dart';voidmain(){runApp(const BreathTrainerApp());}class BreathTrainerApp extends StatelessWidget{const BreathTrainerApp({super.key});@override Widget build(BuildContext context){returnMaterialApp(debugShowCheckedModeBanner: false, title:'🌬️ 呼吸引导', theme: ThemeData(brightness: Brightness.dark, scaffoldBackgroundColor: const Color(0xFF0F172A), primarySwatch: Colors.blue, textTheme: const TextTheme(displayLarge: TextStyle(fontFamily:'Arial', fontWeight: FontWeight.w300),),), home: const BreathTrainerScreen(),);}}class BreathTrainerScreen extends StatefulWidget{const BreathTrainerScreen({super.key});@override State<BreathTrainerScreen>createState()=>_BreathTrainerScreenState();}class _BreathTrainerScreenState extends State<BreathTrainerScreen>with TickerProviderStateMixin{late AnimationController _animationController;late Animation<double>_breathAnimation;int _currentPhase=0;//0: inhale,1: hold,2: exhale,3: hold bool _isRunning=false;int _cycleCount=0;final List<String>_phases=['吸气','屏息','呼气','屏息'];final List<Color>_phaseColors=[Colors.green.shade400, Colors.amber.shade300, Colors.red.shade400, Colors.blue.shade300,];final List<String>_instructions=['缓慢深吸气...','保持呼吸...','缓慢深呼气...','保持空息...',];final List<IconData>_phaseIcons=[Icons.arrow_upward, Icons.pause_circle_outline, Icons.arrow_downward, Icons.pause_circle_outline,];@override voidinitState(){super.initState();_animationController=AnimationController(duration: const Duration(seconds:24), vsync: this,)..addStatusListener((status){ if(status==AnimationStatus.completed){ setState((){ _cycleCount++;_currentPhase=0;});_animationController.reset();if(_isRunning)_animationController.forward();} });_breathAnimation=Tween<double>(begin:0.3,end:1.0).animate(CurvedAnimation(parent:_animationController,curve:Curves.easeInOut),)..addListener((){ setState((){ _currentPhase=(_animationController.value*4).floor();if(_currentPhase>=4)_currentPhase=3;});});} @override void dispose(){ _animationController.dispose();super.dispose();} void _toggleTraining(){ setState((){ _isRunning=!_isRunning;if(_isRunning){ _animationController.forward();} else { _animationController.stop();} });} void _resetTraining(){ setState((){ _isRunning=false;_cycleCount=0;_currentPhase=0;_animationController.reset();});} @override Widget build(BuildContext context){ final currentColor=_phaseColors[_currentPhase];final safeAreaHeight=MediaQuery.of(context).padding.top;return Scaffold(body:Container(decoration:BoxDecoration(gradient:LinearGradient(begin:Alignment.topCenter,end:Alignment.bottomCenter,colors:[ Colors.black87,Color.lerp(Colors.black87,currentColor.withOpacity(0.15),0.3)!,Color.lerp(Colors.black87,currentColor.withOpacity(0.05),0.6)!,Colors.black87,],),),child:SafeArea(child:Column(children:[//顶部状态栏 Padding(padding:EdgeInsets.only(top:safeAreaHeight+8,left:20,right:20),child:Row(mainAxisAlignment:MainAxisAlignment.spaceBetween,children:[ Column(crossAxisAlignment:CrossAxisAlignment.start,children:[ const Text('🌬️ 呼吸引导',style:TextStyle(fontSize:28,fontWeight:FontWeight.bold),),const SizedBox(height:4),Text('${_cycleCount} 次循环',style:const TextStyle(fontSize:16,color:Colors.grey),),],),Container(padding:const EdgeInsets.symmetric(horizontal:16,vertical:6),decoration:BoxDecoration(color:_isRunning?Colors.green.withOpacity(0.2):Colors.red.withOpacity(0.2),borderRadius:BorderRadius.circular(20),),child:Row(children:[ Icon(_isRunning?Icons.play_arrow:Icons.stop,size:18,color:_isRunning?Colors.green:Colors.red,),const SizedBox(width:4),Text(_isRunning?'进行中':'已暂停',style:TextStyle(fontSize:14,color:_isRunning?Colors.green:Colors.red,fontWeight:FontWeight.w600,),),],),),],),),const SizedBox(height:30),//呼吸可视化区域 Expanded(child:Stack(alignment:Alignment.center,children:[//背景脉动圆 AnimatedBuilder(animation:_breathAnimation,builder:(context,child){ return Container(width:320*_breathAnimation.value,height:320*_breathAnimation.value,decoration:BoxDecoration(shape:BoxShape.circle,gradient:RadialGradient(colors:[ currentColor.withOpacity(0.15),currentColor.withOpacity(0.05),],),),);},),//中心呼吸球 AnimatedBuilder(animation:_breathAnimation,builder:(context,child){ return Container(width:180*_breathAnimation.value,height:180*_breathAnimation.value,decoration:BoxDecoration(shape:BoxShape.circle,gradient:RadialGradient(colors:[ currentColor.withOpacity(0.9),currentColor.withOpacity(0.7),],),boxShadow:[ BoxShadow(color:currentColor.withOpacity(0.4),blurRadius:30,spreadRadius:10,),],),child:Center(child:Icon(_phaseIcons[_currentPhase],size:60*_breathAnimation.value,color:Colors.white,),),);},),//阶段指示器 Positioned(bottom:40,child:Container(padding:const EdgeInsets.symmetric(horizontal:24,vertical:12),decoration:BoxDecoration(color:Colors.black87.withOpacity(0.7),borderRadius:BorderRadius.circular(30),border:Border.all(color:currentColor.withOpacity(0.5)),), child: Row(mainAxisSize: MainAxisSize.min, children:[Icon(_phaseIcons[_currentPhase], color: currentColor, size:24,), const SizedBox(width:12), Text(_phases[_currentPhase], style: TextStyle(fontSize:20, fontWeight: FontWeight.bold, color: currentColor,),),],),),),],),), // 指导文字 Padding(padding: const EdgeInsets.only(bottom:24), child: Text(_instructions[_currentPhase], style: TextStyle(fontSize:22, fontWeight: FontWeight.w300, color: currentColor, height:1.5,), textAlign: TextAlign.center,),), // 控制按钮 Container(padding: const EdgeInsets.symmetric(horizontal:24, vertical:20), decoration: BoxDecoration(color: Colors.black87.withOpacity(0.8), borderRadius: const BorderRadius.vertical(top: Radius.circular(30)),), child: Column(children:[// 进度指示器 Row(children: List.generate(4,(index){final isActive=index==_currentPhase;returnExpanded(child: Padding(padding: const EdgeInsets.symmetric(horizontal:4), child: Container(height:8, decoration: BoxDecoration(color: isActive ? _phaseColors[index]:_phaseColors[index].withOpacity(0.3), borderRadius: BorderRadius.circular(4),),),),);}),), const SizedBox(height:24), // 主控制按钮 Row(children:[Expanded(child: OutlinedButton.icon(onPressed: _resetTraining, icon: const Icon(Icons.refresh, size:20), label: const Text('重置', style: TextStyle(fontSize:16)), style: OutlinedButton.styleFrom(foregroundColor: Colors.grey, side: const BorderSide(color: Colors.grey), padding: const EdgeInsets.symmetric(vertical:16), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),),),), const SizedBox(width:16), Expanded(flex:2, child: ElevatedButton.icon(onPressed: _toggleTraining, icon: Icon(_isRunning ? Icons.pause:Icons.play_arrow, size:28,), label: Text(_isRunning ?'暂停训练':'开始训练', style: const TextStyle(fontSize:18, fontWeight: FontWeight.bold),), style: ElevatedButton.styleFrom(backgroundColor: _isRunning ? Colors.red:Colors.green, padding: const EdgeInsets.symmetric(vertical:18), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)), elevation:4,),),),],),],),),],),),),);}}
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/9 18:42:40

计算机毕业设计springboot音乐推荐系统 基于协同过滤算法的个性化音乐推送平台开发 Spring Boot驱动的智能化歌曲推荐服务平台构建

计算机毕业设计springboot音乐推荐系统4ds179 &#xff08;配套有源码 程序 mysql数据库 论文&#xff09; 本套源码可以在文本联xi,先看具体系统功能演示视频领取&#xff0c;可分享源码参考。 随着互联网技术的飞速发展和数字化娱乐的普及&#xff0c;音乐已成为人们日常生活…

作者头像 李华
网站建设 2026/6/9 18:36:17

Java开发者破局指南:跳出内卷,借AI赋能,搭建系统化知识体系

Java开发者破局指南&#xff1a;跳出内卷&#xff0c;借AI赋能&#xff0c;搭建系统化知识体系 在技术迭代加速、AI快速渗透的当下&#xff0c;Java领域的内卷愈发严重——“初级码农”过剩、简历同质化、基础编码工作被AI替代&#xff0c;很多开发者陷入“只会CRUD、不懂底层…

作者头像 李华
网站建设 2026/6/9 22:46:38

php python+vue婚庆礼品网站 开题报告

目录开题报告背景技术选型依据系统功能模块创新点分析预期成果项目技术支持可定制开发之功能亮点源码获取详细视频演示 &#xff1a;文章底部获取博主联系方式&#xff01;同行可合作开题报告背景 婚庆礼品行业近年来发展迅速&#xff0c;线上线下结合的需求日益增长。传统的婚…

作者头像 李华
网站建设 2026/6/9 19:50:26

新手也能上手 AI论文写作软件,千笔 VS Checkjie,专科生专属神器!

随着人工智能技术的迅猛发展&#xff0c;AI辅助写作工具正逐步成为高校学生完成毕业论文的重要助手。尤其是在专科生群体中&#xff0c;面对繁重的论文任务与时间压力&#xff0c;越来越多的学生开始借助AI工具提升写作效率、优化内容质量。然而&#xff0c;市场上AI写作工具种…

作者头像 李华