没有音频播放?没关系——通过色彩心理学和脉冲动画,E-Brufen 用纯视觉手段打造了沉浸式放松体验。本文详解背后的设计决策与实现细节。


在这里插入图片描述

一、设计背景:没有音频的"白噪音"应用?

E-Brufen 的白噪音模块面临一个有趣的约束:鸿蒙 Flutter 的音频播放插件生态尚不成熟audioplayersjust_audio 等主流音频插件在鸿蒙上均无法正常工作。

面对这个限制,我们有两个选择:

方案 做法 结果
A 等待音频插件适配鸿蒙 功能缺失,用户看到的是空白页
B 用视觉设计填补听觉空缺 用户得到完整的放松体验

E-Brufen 选择了 B。核心理念是:视觉本身就是一种疗愈手段——色彩、动效、光影的组合可以创造与音频类似的放松效果。


二、四种场景的色彩体系

// lib/pages/soundscape/soundscape_page.dart
const _scenes = [
  _SceneData('海边入眠', '🌊', Color(0xFF4FC3F7)),   // 天蓝 — 平静、广阔
  _SceneData('雨中办公', '🌧️', Color(0xFF81D4FA)),   // 浅蓝 — 清新、专注
  _SceneData('森林冥想', '🌲', Color(0xFF66BB6A)),   // 森林绿 — 自然、疗愈
  _SceneData('篝火夜读', '🔥', Color(0xFFFFB74D)),   // 暖橙 — 温暖、安全
];

2.1 色彩选择的心理学依据

场景 颜色 Hex 心理暗示 适用时刻
🌊 海边 天蓝 #4FC3F7 平静、广阔、凉爽 睡前放松
🌧️ 雨中 浅蓝 #81D4FA 清新、专注、安宁 办公学习
🌲 森林 绿色 #66BB6A 自然、生长、疗愈 冥想正念
🔥 篝火 橙色 #FFB74D 温暖、舒适、安全 夜晚阅读

★ Insight ─────────────────────────────────────
这些颜色并非随意选择。它们与 E-Brufen 首页三大卡片的渐变色形成呼应:蓝色系对应白噪音卡片、绿色系是品牌主色、橙色系对应情绪日记。用户在首页看到的颜色,进入功能页后会感受到"熟悉的延伸"——这是跨页面的色彩连续性设计。
─────────────────────────────────────────────────


三、场景选择器的视觉设计

3.1 选中/未选中的二元对比

GestureDetector(
  onTap: () {
    setState(() => _selectedScene = i);
    widget.settings.selectedScene = s.name;  // 持久化偏好
  },
  child: Container(
    width: 100,
    margin: const EdgeInsets.only(right: 12),
    decoration: BoxDecoration(
      color: s.color.withValues(
        alpha: active ? 1.0 : 0.5),          // 选中全饱和,未选半透明
      borderRadius: BorderRadius.circular(16),
      border: active
        ? Border.all(color: Colors.white, width: 2)  // 白色边框强化选中
        : null,
    ),
    child: Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        Text(s.emoji, style: const TextStyle(fontSize: 28)),
        const SizedBox(height: 4),
        Text(s.name, style: const TextStyle(
          fontSize: 12, color: Colors.white,
          fontWeight: FontWeight.w600,
        )),
      ],
    ),
  ),
)

视觉对比策略

属性 未选中 选中 目的
不透明度 50% 100% 让选中项"跳出来"
边框 白色 2px solid 明确的选中标记
色彩饱和度 减半 全饱和 色彩本身就是状态指示器

这种"半透明→全饱和"的对比比传统的单选按钮更优雅——不需要额外的 UI 元素(如 Radio),场景色本身就是选中指示。


四、脉冲动画:用视觉模拟呼吸

4.1 AnimationController 配置

class _SoundscapePageState extends State<SoundscapePage>
    with SingleTickerProviderStateMixin {
  late AnimationController _pulse;

  
  void initState() {
    super.initState();
    _pulse = AnimationController(
      vsync: this,
      duration: const Duration(seconds: 2),  // 2秒一个完整呼吸周期
    );
  }
}

为什么是 2 秒?

  • 成年人静息呼吸频率约 12-20 次/分钟
  • 2 秒周期 = 15 次/分钟 → 缓慢放松状态
  • 1 秒太急促(焦虑感),4 秒太慢(沉闷感)

4.2 .repeat(reverse: true) 的关键作用

void _togglePlay() {
  if (_isPlaying) {
    _pulse.repeat(reverse: true);  // 0→1→0→1... 平滑往返
  } else {
    _pulse.stop();
    _pulse.reset();               // 回到初始位置,避免"跳闪"
  }
}
方法 动画轨迹 视觉效果
.repeat() 0→1→(跳回0)→1→… 不连续的"卡顿"感
.repeat(reverse: true) 0→1→0→1→… 平滑的"呼吸"感

4.3 单信号驱动的三重复合动画

AnimatedBuilder(
  animation: _pulse,
  builder: (context, _) {
    return Container(
      // ①大小:120→170px (扩张42%)
      width: 120 + _pulse.value * 50,
      height: 120 + _pulse.value * 50,
      decoration: BoxDecoration(
        shape: BoxShape.circle,
        // ②不透明度:0.25→0.45 (呼吸式明暗)
        color: scene.color.withValues(
          alpha: _isPlaying ? 0.25 + _pulse.value * 0.2 : 0.08),
        // ③光晕:blurRadius 20→50 (脉冲式扩散)
        boxShadow: _isPlaying
          ? [BoxShadow(
              color: scene.color.withValues(alpha: 0.3),
              blurRadius: 20 + _pulse.value * 30,
              spreadRadius: 5,
            )]
          : [],
      ),
    );
  },
)

一个动画值 → 三个视觉属性的同步变化

_pulse.value 0.0 (呼气) 0.5 1.0 (吸气)
直径 120px 145px 170px
不透明度 0.25 0.35 0.45
光晕模糊 20px 35px 50px

★ Insight ─────────────────────────────────────
三条属性都来自 _pulse.value 这一个信号——这是"单信号多输出"的设计模式。好处是三条曲线永远同步,不会出现"球变大了但光晕没跟上"的视觉 Bug。如果用三个独立的 AnimationController,同步将是一个噩梦。
─────────────────────────────────────────────────


五、引导文案的状态切换

Text(
  _isPlaying
    ? '闭眼深呼吸,放轻松… 🌿'
    : '选择场景,按下播放开始放松',
  style: TextStyle(fontSize: 14, color: Colors.grey.shade600),
)

两个状态两套文案——未播放时引导操作,播放时引导放松。这是"上下文感知"的微文案设计。


六、与呼吸球模块的设计对比

维度 白噪音脉冲 呼吸球
动画周期 2 秒 随模式变化 (10-19秒/周期)
动画方向 始终 repeat(reverse) 吸气 forward,呼气 reverse
视觉元素 大小+不透明度+光晕 大小+不透明度+光晕
用户控制 播放/停止 开始/暂停/恢复/退出
色彩来源 场景色(4种) 品牌紫色(固定)

两者使用相同的技术栈(AnimatedBuilder + AnimationController),但服务于不同的体验目标:脉冲是"环境性的背景律动",呼吸球是"引导性的主动练习"。


七、设备的性能考量

在低端鸿蒙设备上(2GB RAM),持续 60fps 的脉冲动画可能造成轻微发热。优化建议:

// 如果需要在低端设备上降级
_pulse = AnimationController(
  vsync: this,
  duration: const Duration(seconds: 3),  // 放慢周期
);
// 或使用 const 构造函数减少重建

上一篇暂停与恢复:呼吸练习的完整生命周期
下一篇倒计时与触觉反馈


作者简介:E-Brufen Dev,Flutter & 鸿蒙开发者。本文的视觉设计基于 E-Brufen 项目在鸿蒙真机上的实际表现撰写。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐