news 2026/4/3 6:38:49

用 Flutter 做一个“会呼吸”的情绪灯 —— 不写 App,只玩光与动效

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
用 Flutter 做一个“会呼吸”的情绪灯 —— 不写 App,只玩光与动效

用 Flutter 做一个“会呼吸”的情绪灯 —— 不写 App,只玩光与动效**

当人们谈起 Flutter,总说:“跨平台”、“高性能 UI”、“Material Design”。
但今天,我们不做一个 App,不写列表、不连 API、不搞登录页。

我们要用 Flutter 做一盏会呼吸的 RGB 情绪灯—— 一个运行在手机或桌面端的圆形渐变灯光,能随心跳般缓慢明暗变化,还能点击切换颜色模式。它像夜里的小夜灯,温柔地照亮你的代码世界。

这不是又一个 Todo List 教程,而是一次对Flutter 动画与视觉表达能力的诗意探索


🌌 项目目标

  • 创建一个全屏柔和发光的圆形“灯”
  • 使用CustomPainter手绘光晕
  • 通过AnimationController实现“呼吸”效果(缩放 + 透明度联动)
  • 点击切换颜色主题:暖白 → 珊瑚红 → 靛蓝 → 薄荷绿
  • 支持背景透明,营造悬浮光感

它没用,但它美。而这,正是技术浪漫主义的意义。


✅ 准备工作

flutter create mood_lampcdmood_lamp

无需第三方依赖,纯原生 Flutter + Dart 实现。


🎨 第一步:设置透明全屏窗口(仅桌面/移动端适用)

为了让灯光“浮”在屏幕上,我们启用透明背景和无 AppBar 的全屏显示。

main.dart
import'package:flutter/material.dart';import'package:window_manager/window_manager.dart';// 可选:用于桌面端去边框voidmain()async{WidgetsFlutterBinding.ensureInitialized();// 【可选】桌面端配置透明无边框if(isDesktop()){awaitwindowManager.ensureInitialized();WindowOptionswindowOptions=constWindowOptions(size:Size(400,400),center:true,transparent:true,frameless:true,);windowManager.waitUntilReadyToShow(windowOptions,()async{awaitwindowManager.show();});}runApp(constMoodLampApp());}boolisDesktop()=>['windows','macos','linux'].contains(Theme.of(null).platform?.toString());classMoodLampAppextendsStatelessWidget{constMoodLampApp({super.key});@overrideWidgetbuild(BuildContextcontext){returnMaterialApp(debugShowCheckedModeBanner:false,home:Scaffold(backgroundColor:Colors.transparent,// 关键:让背景透下去body:constLampBody(),),);}}

💡 提示:安卓/iOS 上也能实现类似效果,只需将Scaffold背景设为透明并使用沉浸式状态栏。


🖌️ 第二步:自定义绘制“光晕”——使用CustomPainter

我们将创建一个动态模糊光圈,靠多重圆环叠加模拟真实灯光扩散。

painters/lamp_painter.dart
// painters/lamp_painter.dartimport'dart:math';import'package:flutter/material.dart';classLampPainterextendsCustomPainter{finalColorcolor;finaldouble intensity;// 呼吸强度 [0.0 - 1.0]LampPainter(this.color,this.intensity);@overridevoidpaint(Canvascanvas,Sizesize){finalcenter=Offset(size.width/2,size.height/2);finalradius=min(size.width,size.height)*0.3;finalpaint=Paint()..blendMode=BlendMode.srcOver;// 绘制多层扩散光圈(从内到外)for(int i=0;i<8;i++){finalalpha=(1.0-i/10)*intensity;finalspread=1.0+i*0.3;paint.shader=RadialGradient(colors:[color.withOpacity(alpha*0.8),Colors.transparent,],).createShader(Rect.fromCircle(center:center,radius:radius*spread),);canvas.drawCircle(center,radius*spread,paint);}}@overrideboolshouldRepaint(covariantLampPainteroldDelegate){returnoldDelegate.color!=color||oldDelegate.intensity!=intensity;}}

💓 第三步:呼吸动画控制器

我们希望灯光像呼吸一样起伏,周期约 4 秒一次。

widgets/breathing_lamp.dart
// widgets/breathing_lamp.dartimport'package:flutter/material.dart';import'../painters/lamp_painter.dart';classBreathingLampextendsStatefulWidget{constBreathingLamp({super.key});@overrideState<BreathingLamp>createState()=>_BreathingLampState();}class_BreathingLampStateextendsState<BreathingLamp>withSingleTickerProviderStateMixin{lateAnimationController_controller;List<Color>_colors=[Colors.white70,Colors.deepOrange.shade300,Colors.indigo.shade200,Colors.tealAccent.withOpacity(0.6),];int _colorIndex=0;ColorgetcurrentColor=>_colors[_colorIndex];@overridevoidinitState(){super.initState();_controller=AnimationController(vsync:this,duration:constDuration(seconds:4),)..repeat(reverse:true);// 自动往返重复}@overridevoiddispose(){_controller.dispose();super.dispose();}voidchangeColor(){setState((){_colorIndex=(_colorIndex+1)%_colors.length;});}@overrideWidgetbuild(BuildContextcontext){returnGestureDetector(onTap:changeColor,child:AnimatedBuilder(animation:_controller,builder:(context,child){// 将动画值映射为呼吸强度:sin 波形更自然finalintensity=sin(_controller.value*pi)*0.6+0.7;returnCustomPaint(painter:LampPainter(currentColor,intensity),child:Container(width:double.infinity,height:double.infinity,alignment:Alignment.center,child:Opacity(opacity:intensity*0.4,child:Text('tap\nto\nchange',textAlign:TextAlign.center,style:TextStyle(fontSize:12,color:currentColor,fontWeight:FontWeight.bold,shadows:[BoxShadow(blurRadius:4,color:Colors.black.withOpacity(0.3),)],),),),),);},),);}}

🔗 最后一步:连接所有部分

更新main.dart中的LampBody
classLampBodyextendsStatelessWidget{constLampBody({super.key});@overrideWidgetbuild(BuildContextcontext){returnContainer(decoration:constBoxDecoration(gradient:LinearGradient(begin:Alignment.topCenter,end:Alignment.bottomCenter,colors:[Color(0xFF111122),Color(0xFF0A0A0F)],// 深色背景衬托光),),child:constBreathingLamp(),);}}

🎥 运行效果

flutter run-dmacos# 或 windows, android, ios

你会看到:

  • 屏幕中央有一团柔和的发光体
  • 光芒缓慢明暗变化,如同呼吸
  • 点击任意位置,灯光切换色调
  • 文字提示轻微闪烁,几乎融入光影

就像房间里的一盏小灯,安静陪伴你深夜 coding。


🧩 技术亮点解析

特性作用
RadialGradient + Shader实现真实光晕扩散
BlendMode.srcOver让多层光叠加更自然
sin(_controller.value * π)模拟生理级呼吸节奏(非线性)
GestureDetector全区域响应点击换色
CustomPainter.shouldRepaint性能优化,避免不必要的重绘

🌈 可扩展方向(让你的灯更聪明)

  • 添加陀螺仪感应:设备倾斜时光晕偏移
  • 接入音乐频谱:灯光随节奏脉动(使用just_audio分析音频)
  • 定时熄灭:30 分钟后自动渐暗(助眠模式)
  • 多灯协作:局域网内多个设备同步呼吸(WebSocket)
  • 打包成屏保或桌面装饰工具

结语:Flutter 是光的画布

我们习惯把 Flutter 当作“做 App 的工具”,
但它本质上是一个像素级可控的视觉引擎

它可以是列表、表单、购物车,
也可以是一束光、一阵风、一声低语。

下次当你打开编辑器,不妨问自己:

“我能用代码,创造出一点美的东西吗?”

这盏小小的“情绪灯”不会帮你赚钱,也不会上架商店,
但它会在某个加班的夜里,轻轻告诉你:

“嘿,你还好吗?”


📦 完整项目结构建议

mood_lamp/ ├── lib/ │ ├── main.dart │ ├── painters/lamp_painter.dart │ └── widgets/breathing_lamp.dart ├── assets/ # 可留空 ├── pubspec.yaml # 无需额外依赖(除 window_manager 可选) └── README.md

GitHub 标题建议写:「A breathing mood lamp built with Flutter — because code can be poetic.」


“Light is not just physics. It’s feeling.”
— 用 Flutter 写动画的人,都是追光者。
欢迎大家加入开源鸿蒙跨平台开发者社区,一起共建开源鸿蒙跨平台生态。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/1 18:29:22

从部署到退役:气象观测Agent全生命周期维护管理精要

第一章&#xff1a;气象观测 Agent 设备维护概述气象观测 Agent 是部署在边缘节点上的轻量级服务程序&#xff0c;负责采集温湿度、气压、风速等环境数据&#xff0c;并将其上报至中心服务器。为确保数据的连续性与准确性&#xff0c;必须对 Agent 设备进行系统化的维护管理。核…

作者头像 李华
网站建设 2026/3/30 12:28:03

mfc100u.dll丢失修复教程:一篇搞定

电脑已经成为我们生活和工作中不可或缺的工具&#xff0c;但有时候难免会出现一些错误提示&#xff0c;如mfc100u.dll丢失。这时候我们就需要修复这个文件&#xff0c;否则某些程序将无法启动。下面一起来看看修复方法吧。 方案一&#xff1a;使用DLL修复工具 最直接且高效的方…

作者头像 李华
网站建设 2026/3/31 21:12:23

计算机小程序毕设实战-基于springboot+微信小程序的集换社卡牌的交易系统小程序基于微信小程序的集换式卡牌交易系统【完整源码+LW+部署说明+演示视频,全bao一条龙等】

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

作者头像 李华
网站建设 2026/3/28 4:04:51

【课程设计/毕业设计】基于springboot+微信小程序的的交通违法有奖曝光平台交通违法有奖举报制度【附源码、数据库、万字文档】

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

作者头像 李华
网站建设 2026/4/2 7:33:26

【接口测试】4_Postman _全局变量和环境变量

文章目录一、概念二、全局变量-设置和获取2.1 全局变量设置-2种方法2.2 全局变量获取-2种方法三、环境变量-设置和获取3.1 环境变量设置-2种方法3.2 环境变量获取-2种方法四、环境变量-说明一、概念 1、全局变量&#xff1a;全局变量是全局唯一的&#xff0c;不可重复定义的变…

作者头像 李华
网站建设 2026/4/3 4:02:15

java接口

1.概念&#xff1a;"一种引用类型"用于定义一组抽象方法和常量&#xff08;默认为public static final&#xff09;。接口通过implements关键字被类实现&#xff0c;需要重写所有抽象方法&#xff0c;除非是抽象类&#xff0c;但是一般不会是抽象类&#xff0c;支持多…

作者头像 李华