当前位置: 首页 > news >正文

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,),),),],),],),),],),),),);}}
http://www.jsqmd.com/news/358702/

相关文章:

  • 论文写作新选择:11个AI辅助网站全解析
  • Flutter for OpenHarmony 动效实战:打造一个会“跳”的幸运骰子应用
  • 【开题答辩全过程】以 基于SSM的高校宿舍管理系统为例,包含答辩的问题和答案
  • Flutter for OpenHarmony 可视化教学:A* 寻路算法的交互式演示
  • IT Hit WebDAV Server Engine for .NET 15.4.16
  • USB协议 键盘流量 鼠标流量
  • 2026Q1上海财税公司推荐榜|工商注册+代账+注册注销+资质代办全服务合规赋能 - 品牌智鉴榜
  • 拍照手机影像能力:全链路能力及专业测评考查参数 - 指南
  • 开发者必看:2026年最新18个Github镜像站,下载部署全搞定
  • 一、单例模式
  • 人工智能在智能制造中的应用与未来前景 - 详解
  • 文【牛客tracker 每日一题】
  • 小程序毕设项目推荐-基于微信小程序的温州博物馆系统博物馆展览预约参观讲解基于springboot+小程序的温州博物馆小程序的设计与实现【附源码+文档,调试定制服务】
  • 写论文省心了 8个AI论文工具测评:继续教育毕业论文写作全攻略
  • 计算机小程序毕设实战-基于springboot+小程序的温州博物馆小程序的设计与实现基于Springboot+Uniapp的博物馆预约微信小程序【完整源码+LW+部署说明+演示视频,全bao一条龙等】
  • 看完就会:降AI率工具,千笔AI VS 知文AI,MBA专属更高效!
  • WPF 自定义控件库名称空间
  • 小程序毕设项目:基于springboot+小程序的温州博物馆小程序的设计与实现(源码+文档,讲解、调试运行,定制等)
  • 导师严选!自考必备的AI论文网站 —— 千笔·专业学术智能体
  • 真心不骗你!本科生专属降AI率网站 千笔·专业降AIGC智能体 VS 锐智 AI
  • 【毕业设计】基于springboot+小程序的温州博物馆小程序的设计与实现(源码+文档+远程调试,全bao定制等)
  • 毕业论文无忧:11个AI辅助写作网站精选
  • AI元人文:跨文化共生与文明新契约
  • 小程序计算机毕设之基于springboot+小程序的温州博物馆小程序基于微信小程序的温州博物馆系统博物馆展览预约参观讲解(完整前后端代码+说明文档+LW,调试定制等)
  • AI赋能论文写作:11款跨学科工具推荐
  • 【计算机毕业设计案例】基于Springboot+Uniapp的博物馆预约微信小程序设计与实现基于springboot+小程序的温州博物馆小程序的设计与实现(程序+文档+讲解+定制)
  • 学术研究利器:11款AI论文写作工具盘点
  • 人工智能驱动的科研新范式及学科应用研究
  • 科研效率拉满!2026 优质 AI 写论文软件精选(实测推荐)
  • 大模型提示词:零样本(Zero-shot)和少样本(Few-shot)介绍(Zero shot、Few shot)