Jetpack Compose で Haptics に対応する

# Haptics とは

利用者に力、振動、動きなどを与えることで皮膚感覚フィードバックを得るテクノロジーのことで、ことAndroidにおいては触覚による刺激生成に貢献するハードウェアとソフトウェアの機能を指します。

https://source.android.com/docs/core/interaction/haptics

# Androidでの Haptics Framework
UX原則として以下の3パターンが推進されています。

![hapticsSAC12_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223159/89f089bc-fdd4-a27c-6707-455c3cf7d187.png)

#### 1. Buzzy

ポケベルやガラケー時代に利用されていた低品質ながらも電力効率の高いERMブザーベースのバイブレーション

#### 2. Clear

非連続の状態変化（Power On/Offやプロセス中のバイナリ変化） をサポート
クリアなHapticsは1つのInput Eventに対して1つのHapticなど単一のエンティティとして生成されます

![hapticsSAC14_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223159/89db1019-7d67-50e8-d916-6779f43ed045.png)


#### 3. Rich

単一のインパルスベースの効果を超える成長中のカテゴリ。Androidとしては高度な構成可能性と細かい粒度での調整可能性を備えたリッチなHaptics をサポートすることを目指している。

![hapticsSAC15_1.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223159/96d75468-3044-351d-437b-70aa2c785ed1.gif)

# Sample

振動エフェクトとして3つのカテゴリが用意されているためカテゴリごとのアクションを含んだButtonを作成します。

```HapticsRoute.kt
@Composable
private fun PredefinedEffectsButton(
    label: String,
    effect: Int,
    application: Application
) {
    val vibrator = ContextCompat.getSystemService(application, Vibrator::class.java)!!
    TextButton(
        onClick = {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
                vibrator.vibrate(VibrationEffect.createPredefined(effect))
            }
        },
    ) {
        Text(label)
    }
}

@Composable
private fun HapticFeedbackConstantsButton(
    label: String,
    effect: Int,
) {
    val view = LocalView.current
    TextButton(
        onClick = {
            view.performHapticFeedback(effect)
        },
    ) {
        Text(label)
    }
}

@Composable
private fun CompositionPrimitivesButton(
    label: String,
    effect: Int,
    application: Application
) {
    val vibrator = ContextCompat.getSystemService(application, Vibrator::class.java)!!
    TextButton(
        onClick = {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
                vibrator.vibrate(
                    VibrationEffect.startComposition()
                        .addPrimitive(effect)
                        .compose()
                )
            }
        },
    ) {
        Text(label)
    }
}
```

それぞれのカテゴリは以下をご参照ください。

1. [VibrationEffect#createPredefined](https://developer.android.com/reference/android/os/VibrationEffect#createPredefined(int))
1. [View#performHapticFeedback](https://developer.android.com/reference/android/view/View#performHapticFeedback(int))
1. [VibrationEffect.Composition](https://developer.android.com/reference/android/os/VibrationEffect.Composition)

最後にScreenの定義を行います。

```HapticsRoute.kt
@Composable
fun HapticsRoute(
    application: Application
) {
    Screen(
        homeUiState = viewModel.homeUiState, onButtonClicked = viewModel::onButtonClicked,
        onSnackbarMessage = viewModel::onSnackbarMessage, scrollState = viewModel.scrollState,
    )
}

@Composable
private fun Screen(application: Application) {
    Column(
        modifier = Modifier.fillMaxSize(),
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        // PREDEFINED_EFFECTS
        PredefinedEffectsButton(
            label = "EFFECT_TICK",
            effect = VibrationEffect.EFFECT_TICK,
            application = application
        )

        // HAPTIC_FEEDBACK_CONSTANTS
        HapticFeedbackConstantsButton(
            label = "CONFIRM",
            effect = HapticFeedbackConstants.CONFIRM
        )

        // COMPOSITION_PRIMITIVES
        CompositionPrimitivesButton(
            label = "EFFECT_TICK",
            effect = VibrationEffect.Composition.PRIMITIVE_LOW_TICK,
            application = application
        )
    }
}
```

その他カテゴリごとに何種類もEffectが用意されているのですぐに利用できます。

#### 最後に

- [easing](https://developer.android.com/reference/android/os/VibrationEffect#createWaveform(long%5B%5D,%20int%5B%5D,%20int))や[Custom Effects](https://source.android.com/docs/core/interaction/haptics/haptics-ux-foundation#create-effect)なども比較的簡単に利用できるため前述のRichパターンを独自に実現することができそうです。
- 実際に組み込む際には、[最小要件のフローチャート](https://source.android.com/docs/core/interaction/haptics/haptics-implement#implement-constants)を参照すると良さそうです。