はじめに
Jetpack Composeでの触覚フィードバック(Haptic)の情報がまだ少なかったので、LazyColumnを使った実装例をまとめました。
意外と簡単に試せるので、ぜひ端末で触って体験してみてください!
対象
- Jetpack Composeを業務で使っているAndroidエンジニア
- LazyColumnでドラムロール風UIやスナップスクロールを設計したい
- 触覚フィードバック(Haptic)APIの適用例を知りたい方
実装
ポイント
選択状態が変わったときだけHapticを発火する
- LazyListState.firstVisibleItemIndexを監視
- selectedIndexが変わったときだけSegmentTick
- LaunchedEffectとsnapshotFlowを組み合わせて差分を検出
Pixel系では動作確認できましたが、端末によっては動作しない場合があります。
コード
端末を繋いでAndroidStudioでRun Preview
すれば体験できるサンプルです
@Preview(showBackground = true)
@Composable
fun HapticDrumrollPreview() {
val listState = rememberLazyListState()
val haptic = LocalHapticFeedback.current
var selectedIndex by remember { mutableIntStateOf(listState.firstVisibleItemIndex) }
LaunchedEffect(listState) {
snapshotFlow { listState.firstVisibleItemIndex }
.distinctUntilChanged()
.collect { newIndex ->
if (newIndex != selectedIndex) {
haptic.performHapticFeedback(HapticFeedbackType.SegmentTick)
selectedIndex = newIndex
}
}
}
LazyColumn(
state = listState,
modifier = Modifier
.fillMaxWidth()
.height(200.dp),
contentPadding = PaddingValues(vertical = 80.dp)
) {
items(20) { index ->
Box(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 8.dp),
contentAlignment = Alignment.Center
) {
Text(
"項目 $index",
fontSize = 20.sp,
color = if (index == selectedIndex) Color.Blue else Color.Unspecified
)
}
}
}
}
応用のアイデア
触覚フィードバックは「値が一段階進む」ようなUIと相性が良いです。
今回の仕組みを応用すると、例えば次のようなケースで利用できます。
-
ドラムロール風の時刻ピッカー
→ 1分ごとに「コトッ」とフィードバックが返ると直感的 -
音量や速度などの段階選択
→ 数値が一段階変わるごとに軽く震えると操作感が増す -
スロットマシン風UI
→ リールが止まる瞬間に「ガチッ」と響くとゲーム感が出る -
縦方向のナビゲーションセレクター
→ リストを切り替えるときに切れ目がわかりやすくなる
触覚フィードバックの種類
Compose では様々な種類の HapticFeedback が用意されています。
大きく分けると次のような用途があります。
-
操作の確定や切り替え
- Confirm
- ToggleOn / ToggleOff
- VirtualKey
-
操作の中断やエラー
- Reject
- GestureEnd
-
連続的な操作の節目
- SegmentTick(今回使用)
- SegmentFrequentTick
- TextHandleMove
-
一般的な操作
- ContextClick
- KeyboardTap(API 33 以降)
- LongPress
- GestureThresholdActivate
特に今回使った SegmentTick は「スクロールで項目が切り替わる瞬間」に最適です。
他のタイプも UI に合わせて使い分けると、操作感がぐっと良くなります。
参考文献