news 2026/3/26 20:38:08

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/3/26 20:00:51

【开题答辩全过程】以 基于ssm的公寓出租管理系统的设计与实现为例,包含答辩的问题和答案

个人简介 一名14年经验的资深毕设内行人&#xff0c;语言擅长Java、php、微信小程序、Python、Golang、安卓Android等 开发项目包括大数据、深度学习、网站、小程序、安卓、算法。平常会做一些项目定制化开发、代码讲解、答辩教学、文档编写、也懂一些降重方面的技巧。 感谢大家…

作者头像 李华
网站建设 2026/3/22 21:36:36

小程序毕设项目推荐-基于微信小程序的温州博物馆系统博物馆展览预约参观讲解基于springboot+小程序的温州博物馆小程序的设计与实现【附源码+文档,调试定制服务】

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围&#xff1a;&am…

作者头像 李华
网站建设 2026/3/23 22:22:20

真心不骗你!本科生专属降AI率网站 千笔·专业降AIGC智能体 VS 锐智 AI

在AI技术迅速发展的今天&#xff0c;越来越多的本科生开始借助AI工具进行论文写作&#xff0c;以提高效率和内容质量。然而&#xff0c;随之而来的“AI率超标”问题却让不少学生陷入困境。随着各大查重系统对AI生成内容的识别能力不断提升&#xff0c;论文中的AI痕迹一旦被检测…

作者头像 李华
网站建设 2026/3/22 23:30:00

【毕业设计】基于springboot+小程序的温州博物馆小程序的设计与实现(源码+文档+远程调试,全bao定制等)

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围&#xff1a;&am…

作者头像 李华
网站建设 2026/3/22 22:14:47

掌握SEO艺术:利用长尾关键词全面提升网站优化效果

在SEO优化中&#xff0c;长尾关键词的运用显得尤为重要。通过对用户需求的深入分析&#xff0c;可以制定出更精准的关键词策略&#xff0c;从而为网站带来优质流量。长尾关键词不仅具有较低的竞争度&#xff0c;还能更好地满足特定用户的搜索意图&#xff0c;让网站在搜索引擎中…

作者头像 李华
网站建设 2026/3/22 16:06:23

跨学科必备:11款AI论文写作辅助网站推荐

近年来&#xff0c;人工智能语言模型的飞速发展彻底改变了学术研究的格局。尤其是自 2022 年 11 月 OpenAI 发布 ChatGPT 以来&#xff0c;AI 工具在学术界的应用日益广泛&#xff0c;帮助科研人员节省时间、提高效率&#xff0c;从而专注于更有价值的任务。AI 学术工具本质上是…

作者头像 李华