冥想呼吸助手:在 AR 眼镜上实现沉浸式呼吸引导

34 阅读6分钟

冥想呼吸助手:在 AR 眼镜上实现沉浸式呼吸引导

背景

作为一个长期伏案工作的程序员,我经常感到肩颈僵硬、精神紧张。之前尝试过一些冥想 App,但每次都要盯着手机屏幕,总觉得少了点什么——直到我接触了 Rokid AR 眼镜。 想象一下:你闭上眼睛,开始冥想,但又想看到呼吸节奏的引导。手机屏幕?太打扰了。智能手表?屏幕太小。AR 眼镜刚刚好,既能看到引导信息,又不会完全隔绝环境光。

于是,我开发了这款「冥想呼吸助手」。

技术选型

这次开发选择了 CXR-M SDK 而不是灵珠平台,主要考虑:

  1. **灵活性更高:**CXR-M SDK 是纯 Android 开发,可以用熟悉的 Kotlin 和 Android Studio
  2. **实时交互:**呼吸引导需要频繁更新显示内容,SDK 的流式传输能力正好满足
  3. **TTS 语音:**呼吸阶段切换时需要语音提示,SDK 内置了 TTS 能力

技术方案

整体架构

采用简单的 MVC 架构,因为功能不复杂,没必要过度设计:

核心数据结构

冥想的核心是「呼吸模式」,我参考了一些心理学和呼吸疗法的资料,预设了 5 种常用模式:

enum class BreathingPattern(
    val displayName: String,
    val inhale: Int,      // 吸气秒数
    val hold: Int,        // 屏息秒数
    val exhale: Int,      // 呼气秒数
    val holdAfter: Int,   // 呼后屏息秒数
    val description: String
) {
    RELAXING("放松呼吸", 4, 0, 4, 0, "简单有效的放松方式"),
    BOX("箱式呼吸", 4, 4, 4, 4, "海军海豹队使用的减压法"),
    FOUR_SEVEN_EIGHT("4-7-8呼吸", 4, 7, 8, 0, "帮助入睡的经典呼吸法"),
    CALM("平静呼吸", 4, 2, 6, 0, "缓解焦虑情绪"),
    DEEP("深度呼吸", 6, 2, 8, 2, "深度放松身心");
}

最常用的是 4-7-8 呼吸法,这是 Andrew Weil 医生推广的助眠技术,对缓解焦虑也很有效。

呼吸循环的实现

这是整个应用的核心逻辑,用 CountDownTimer 实现多阶段计时:

private enum class BreathingPhase {
    INHALE, HOLD, EXHALE, HOLD_AFTER
}

private fun startPhaseTimer() {
    val pattern = currentCourse?.pattern ?: return

    // 根据当前阶段获取秒数
    val seconds = when (currentPhase) {
        BreathingPhase.INHALE -> pattern.inhale
        BreathingPhase.HOLD -> pattern.hold
        BreathingPhase.EXHALE -> pattern.exhale
        BreathingPhase.HOLD_AFTER -> pattern.holdAfter
    }

    // 如果某阶段为0秒,直接跳过
    if (seconds == 0) {
        moveToNextPhase()
        return
    }

    updatePhaseDisplay(currentPhase, seconds)

    phaseTimer = object : CountDownTimer(seconds * 1000L, 1000) {
        override fun onTick(millisUntilFinished: Long) {
            phaseTimeLeft = (millisUntilFinished / 1000).toInt()
            updatePhaseDisplay(currentPhase, phaseTimeLeft)
        }
        override fun onFinish() {
            moveToNextPhase()
        }
    }.start()
}

private fun moveToNextPhase() {
    currentPhase = when (currentPhase) {
        BreathingPhase.INHALE -> if (pattern.hold > 0) HOLD else EXHALE
        BreathingPhase.HOLD -> EXHALE
        BreathingPhase.EXHALE -> if (pattern.holdAfter > 0) HOLD_AFTER else INHALE
        BreathingPhase.HOLD_AFTER -> INHALE
    }

    // 语音提示当前阶段
    val ttsText = when (currentPhase) {
        BreathingPhase.INHALE -> "吸"
        BreathingPhase.HOLD -> "屏"
        BreathingPhase.EXHALE -> "呼"
        BreathingPhase.HOLD_AFTER -> "屏"
    }
    RokidGlassesManager.sendTts(ttsText)

    // 同步到眼镜
    sendCurrentPhaseToGlasses()

    startPhaseTimer()
}

这个实现有个细节:每个呼吸模式都有 4 个阶段,但有些阶段可能为 0(比如「放松呼吸」模式没有屏息阶段)。代码里做了判断,0 秒的阶段会自动跳过。

眼镜端显示格式

发送到眼镜的内容需要精心设计,信息要清晰但不杂乱:

🧘 快速放松

  吸气
  3 秒

⏱ 2:45 / 3:00

吸→屏→呼→屏
4  0  4  0

上面的区域显示课程名和当前阶段,中间是倒计时,下面是呼吸模式参数。用户一眼就能看清楚自己该怎么呼吸。 生成这个格式的代码:

private fun sendCurrentPhaseToGlasses() {
    val course = currentCourse ?: return
    val pattern = course.pattern

val phaseName = when (currentPhase) {
    BreathingPhase.INHALE -> "吸气"
    BreathingPhase.HOLD -> "屏息"
    BreathingPhase.EXHALE -> "呼气"
    BreathingPhase.HOLD_AFTER -> "屏息"
}

val text = buildString {
    appendLine("🧘 ${course.name}")
    appendLine()
    appendLine("      $phaseName")
    appendLine("      $phaseTimeLeft 秒")
    appendLine()
    val minutes = totalSecondsLeft / 60
    val seconds = totalSecondsLeft % 60
    appendLine("⏱ ${String.format("%d:%02d", minutes, seconds)} / ${course.durationMinutes}:00")
    appendLine()
    appendLine("吸→屏→呼→屏")
    appendLine("${pattern.inhale}  ${pattern.hold}  ${pattern.exhale}  ${pattern.holdAfter}")
}

RokidGlassesManager.sendBreathingGuide(text)

}

开发过程中的问题

问题一:Timer 的生命周期管理

一开始没有在 Activity 销毁时取消 Timer,导致切换课程时会有多个 Timer 同时运行,界面显示混乱。 解决方案:在 onDestroy() 中取消所有 Timer:

override fun onDestroy() {
    super.onDestroy()
    phaseTimer?.cancel()
    totalTimer?.cancel()
}

问题二:暂停/继续功能

用户可能中途需要暂停,但 CountDownTimer 没有暂停方法,只能取消。 解决方案:记录当前剩余时间,暂停时取消 Timer,继续时用剩余时间重新创建 Timer:

private fun pauseMeditation() {
    isPaused = true
    phaseTimer?.cancel()
    totalTimer?.cancel()
    binding.btnStart.text = "继续"
}

private fun resumeMeditation() {
    isPaused = false
    startTotalTimer()  // 用剩余时间重建
    startPhaseTimer()
    binding.btnStart.text = "暂停"
}

问题三:TTS 语音重叠

阶段切换很快时,TTS 语音可能会重叠播放。 目前的处理是保持简单,因为呼吸节奏通常不会太快(每个阶段至少 2 秒)。如果后续优化,可以考虑用队列管理 TTS 播放。

最终效果

功能清单

● 5 种呼吸模式:放松呼吸、箱式呼吸、4-7-8 呼吸、平静呼吸、深度呼吸 ● 5 种冥想课程:快速放松(3分钟)、睡前放松(5分钟)、深度放松(10分钟)、专注力训练(5分钟)、缓解焦虑(3分钟) ● 实时眼镜同步:呼吸阶段、倒计时、进度实时显示在眼镜上 ● 语音引导:每个阶段切换时语音提示「吸」「屏」「呼」 ● 统计功能:今日冥想次数、累计时长、连续打卡天数

使用流程

  1. 打开 App,选择冥想类型和课程
  2. 连接 Rokid 眼镜
  3. 点击「开始冥想」
  4. 跟随眼镜上的引导呼吸
  5. 完成后查看统计数据

眼镜端效果

冥想进行中:

🧘 睡前放松

   吸气
   2 秒

⏱ 3:42 / 5:00

吸→屏→呼→屏
4  7  8  0

完成后:

完成后:

🎉 练习完成

今日冥想:1次
累计时长:8分钟

连续打卡:3天 🔥

💪 坚持练习,效果更好!

总结

项目亮点

  1. 沉浸式体验:眼镜显示呼吸引导,不需要盯着手机,更容易进入冥想状态
  2. 科学的呼吸模式:预设了多种经过验证的呼吸方法,不是随便设计的
  3. 简单易用:选择课程 → 开始 → 跟着呼吸,三步搞定
  4. 语音引导:闭上眼睛也能知道什么时候该吸气、呼气

不足与改进方向

  1. 缺少背景音乐:冥想时配合舒缓的音乐效果更好,后续可以加入
  2. 统计功能简单:目前只用了 SharedPreferences,数据多了可能需要用 Room 数据库
  3. 连续打卡计算:目前的打卡天数计算比较简单,没有处理跨日期的情况
  4. 可视化呼吸圆圈:手机端有个呼吸圆圈动画,但效果还可以更精致 总的来说,这是一个小而美的应用,解决了一个具体的问题。AR 眼镜在健康类应用上有天然优势,因为可以做到「看得到但不会被打扰」,非常适合冥想、运动这类场景。

项目地址:MeditationHelper/ 开发环境: ● Android Studio Hedgehog ● Kotlin 1.9.0 ● CXR-M SDK 1.0.1 ● minSdk 28 (Android 9.0)