位置:首页 > Kotlin > Jetpack Compose 震动效果实现技巧与玩法大全

Jetpack Compose 震动效果实现技巧与玩法大全

时间:2026-08-21  |  作者:火苗实验室  |  阅读:0

上周我在公司摸鱼的时候,发现 Github 上有一个开源库叫 Jindong,没错,听起来就是京东!

我一开始还以为自己看错了,京东什么时候开始干这玩意儿了?后面才发现是误会了。

Jindong 是什么

Jindong 为 Compose 跨平台应用(支持 Android 和 iOS)提供了声明式的触觉反馈 DSL

它基于 Compose 构建,让你能通过简单直观的 API 定义复杂的触觉模式。

该开源库的作者是来自韩国的开发者。“Jindong”[tindoo]()在韩语中意为“振动”。
好朴实无华的库名称,如果是中文名那岂不就是——Zhendong?

当前移动 UI 开发已经成功转向声明式范式,但与硬件 API 的交互,尤其是触觉反馈,仍然主要依赖命令式编程。

所以这个库的初衷,就是让触觉反馈的交互也转向声明范式。

它解决了在 Compose Multiplatform 应用中实现触觉反馈的复杂性。借助 Compose,开发者现在可以将触觉模式以结构化描述的形式进行定义。

也就是说,它提供了一种声明范式的方式调用硬件 API,同时还支持跨平台。

传统震动

先看看传统的命令式方式。

使用标准 Android API 创建波形振动时,需要并行的时序和振幅值数组:

Column(
    modifier = Modifier.fillMaxSize(),
) {
    Button(onClick = {
        val timings = longArrayOf(0, 50, 100, 50, 100, 50)
        val amplitudes = intArrayOf(0, 255, 0, 128, 0, 255)
        val effect = VibrationEffect.createWa veform(timings, amplitudes, -1)
        defaultVibrator.vibrate(effect)
    }) {
        Text("Vibrate")
    }
}

如果你要调整某个节拍的持续时间,就得重新计算数组索引。

更麻烦的是,这种逻辑是平台相关的。iOS 的 Core Haptics 也需要完全不同的代码实现。

声明式

Jindong 把这些底层细节抽象成了一种 DSL。

开发者只需要定义反馈结构,库会负责执行。

先添加权限和依赖

<uses-permission android:name="android.permission.VIBRATE" />
implementation("io.github.compose-jindong:jindong:1.0.0")

再用 DSL 定义震动

var count by remember { mutableIntStateOf(0) }

JindongProvider { // 需要提供一个 JindongProvider
    if (count > 0) { // 防止第一次进来就震动
        Jindong(count) { // 传入一个 key,当 key 发生变化的时候,就会震动
            Sequence {
                repeat(3) { // 重复三次震动
                    Haptic(50.ms, HapticIntensity.STRONG)
                    Delay(100.ms)
                }
            }
        }
    }

}

Column(
    modifier = Modifier.fillMaxSize(),
) {
    Button(onClick = {
        count++
    }) {
        Text("Vibrate")
    }
}

在这里,开发者只需要定义触觉模式,也就是一个重复三次的序列。

至于平台相关的执行细节,库会自己处理。

这种差异不只是语法不同,关键在于谁来控制执行过程。

这也正是旧式 View 写法和新式 Compose 写法的核心差别:

命令式思维声明式思维
“先振动 50ms,然后等待 100ms,再振动 50ms”“我想要一个 50ms-100ms-50ms 的震动模式”
“循环三次,中间有延迟,启动震动”“我想要三次重复”

声明式编程关注的是定义目标的逻辑和关系,而不是指定如何实现它们。

“描述要做什么,而不是怎么做。”

生命周期管理

在命令式触觉反馈中,生命周期管理是最大的痛点之一。

如果用户在长时间振动模式播放过程中导航离开当前页面,会发生什么?

在命令式编程里,你必须手动在 onPauseonDispose 中调用 vibrator.cancel()

而使用 Jindong 时,触觉模式会自动绑定到 Compose 的生命周期。

  • 自动取消:当 Composable 离开组合时,触觉引擎会立即停止。
  • 响应式更新:就像 LaunchedEffect 一样,如果在模式播放过程中 keys 发生变化,Jindong 会自动取消当前模式,并根据新状态重新开始执行。
/**
 * 当 keys 发生变化时,触发触觉模式执行的可组合函数。
 */
@Composable
fun jindong(
    vararg keys: Any,
    content: @Composable JindongScope.() -> Unit,
) {
    val pattern = rememberHapticPattern(content)
    val executor = LocalHapticExecutor.current
    LaunchedEffect(*keys) {
        executor.execute(pattern)
    }
}

这正是 Compose 的一大优势。

你无需特别关心生命周期。

当你离开组合时,当前的副作用会自动结束。

搞点例子

以下是一些 Jindong 的实用场景。

下面的例子因为篇幅我没有显示的指定 JindongProvider,实际上所有的 Jindong 都需要在 JindongProvider 内部。

屏幕进入反馈

使用 Unit 作为触发键,可以在进入屏幕时立即触发热反馈:

@Composable
fun WelcomeScreen() {
    // 页面出现时执行一次
    Jindong(Unit) {
        Haptic(100.ms, HapticIntensity.MEDIUM)
    }
}

按钮点击反馈

@Composable
fun TapButton() {
    var taps by remember { mutableIntStateOf(0) }
    if (taps > 0) {
        Jindong(taps) {
            Haptic(100.ms, HapticIntensity.LIGHT)
        }
    }
    Button(onClick = { taps++ }) {
        Text("Tap")
    }
}

淡出效果

@Composable
fun FadeOutHaptic(trigger: Int) {
    Jindong(trigger) {
        RepeatWithIndex(5) { index ->
            val intensity = HapticIntensity.Custom(1.0f - (index * 0.2f))
            Haptic(50.ms, intensity)
            Delay(100.ms)
        }
    }
}

模拟心跳

@Composable
fun HeartbeatHaptic(trigger: Int) {
    Jindong(trigger) {
        Repeat(5) {

            // Lub(强脉冲)
            Haptic(60.ms, HapticIntensity.STRONG)
            Delay(80.ms)

            // Dub(弱脉冲)
            Haptic(40.ms, HapticIntensity.MEDIUM)
            Delay(400.ms)
            
        }
    }
}

强烈建议各位试一下,这个特别像!

内部原理:Compose Runtime 与 CMP

Compose Runtime 的作用是什么?

Compose Runtime 是一个与 UI 渲染无关的树管理引擎。

Jindong 依赖于 Compose 运行时的两个核心组件:

  • ComposeNode:这是一个通用函数,用于将节点插入到树中。在 Jindong 中,每个 DSL 函数(如 HapticDelaySequence)都会在内部调用 ComposeNode,以创建特定的 HapticNode 实现。
  • Applier:运行时需要知道如何将这些节点连接起来。Applier 负责处理 HapticNode 树的结构更新,该树会在播放前被遍历,以收集 ScheduledHapticEvent 事件。

正如 Compose UI 通过构建 LayoutNode 树来渲染像素那样,Jindong 是通过构建 HapticNode 树来合成振动效果的。

这使得我们能够运用标准的 Compose 特性,比如 SideEffectremember,再结合 Kotlin 的标准控制流,像 if/when/for,自然而然地构建出动态的触觉体验。

Compose Multiplatform 实现

Jindong 遵循标准的 CMP(Compose Multiplatform)架构:

  1. 通用层(Common Layer) :包含 Compose DSL 定义、节点(Nodes)以及 HapticPattern 数据模型。所有时间计算和模式编译都在此层完成,确保跨平台行为的一致性。
  2. 平台层(Platform Layer,即执行器 Executor)
    • Android:将 HapticPattern 转换为 VibrationEffect(使用 VibrationEffect.createWa veform)。
    • iOS:将 HapticPattern 转换为 CHHapticPattern,用于 Core Haptics。

Android 和 iOS 对振动强度的处理方式不同。

Android 使用振幅整数(1–255),而 iOS 的 Core Haptics 使用归一化的浮点值(0.0–1.0)。

Jindong 提供了一个统一的抽象层。

开发者可以使用归一化的百分比,例如 intensity = 0.5f,来定义强度,无需关心平台特定的数学转换。

总结

Jindong 旨在解决在 Compose Multiplatform 应用中实现触觉反馈的复杂性。

通过利用 Compose 运行时,开发者可以将触觉模式定义为结构化描述,而非一系列电机命令序列。

各位还在等什么,赶紧用起来试试呀!

免责声明:文中图文均来自网络,如有侵权请联系删除,心愿游戏发布此文仅为传递信息,不代表心愿游戏认同其观点或证实其描述。

相关文章

更多

精选合集

更多

大家都在玩

热门话题

大家都在看

更多