Compose によるロータリー入力

ロータリー入力とは、回転するスマートウォッチの入力のことです。ユーザーがスマートウォッチの操作に費やす時間は平均すると数秒のみですす。ロータリー入力を使用してさまざまなタスクをすばやく完了できるようにすることで、ユーザー エクスペリエンスを改善できます。

ほとんどのスマートウォッチでは、ロータリー入力の主な 3 つのソースとして、回転するサイドボタン(RSB)と、物理的なベゼルまたはタッチベゼル(画面周囲の円形のタッチゾーン)があります。予想される動作は入力の種類によって異なる場合がありますが、すべての重要な操作でロータリー入力をサポートするようにしてください。

スクロール

ほとんどのユーザーは、アプリがスクロール操作に対応していることを想定しています。コンテンツが画面でスクロールしたら、ロータリー操作に応じてユーザーに視覚的なフィードバックを提供します。視覚的なフィードバックには、縦方向のスクロール用の位置インジケーターページ インジケーターを追加できます。

ScalingLazyColumnPicker はデフォルトでスクロール操作をサポートしています。 ただし、これらのコンポーネントを Scaffold 内に配置する必要があります。Scaffold 基本的なレイアウト構造を提供します。 スクロール インジケーター用のスロットがすでに用意されています。宛先 スクロールの進捗状況を表示する、 リスト状態オブジェクトを返します。次のコード スニペットをご覧ください。

val listState = rememberScalingLazyListState()
Scaffold(
    positionIndicator = {
        PositionIndicator(scalingLazyListState = listState)
    }
) {
    // ...
}

ScalingLazyColumn のスナップ動作を設定するには、次のコマンドを使用します。 ScalingLazyColumnDefaults.snapFlingBehavior を使用します。 コード スニペット:

val listState = rememberScalingLazyListState()
Scaffold(
    positionIndicator = {
        PositionIndicator(scalingLazyListState = listState)
    }
) {

    val state = rememberScalingLazyListState()
    ScalingLazyColumn(
        modifier = Modifier.fillMaxWidth(),
        state = state,
        flingBehavior = ScalingLazyColumnDefaults.snapFlingBehavior(state = state)
    ) {
        // Content goes here
        // ...
    }
}

カスタム アクション

アプリでのロータリー入力に応答するカスタム アクションを作成することもできます。たとえば、メディアアプリのズームインやズームアウト、音量の調節には、ロータリー入力を使用します。

コンポーネントが音量コントロールなどのスクロール イベントをネイティブにサポートしていない場合は、スクロール イベントをご自分で処理できます。

// VolumeScreen.kt

val focusRequester: FocusRequester = remember { FocusRequester() }

Column(
    modifier = Modifier
        .fillMaxSize()
        .onRotaryScrollEvent {
            // handle rotary scroll events
            true
        }
        .focusRequester(focusRequester)
        .focusable(),
) { ... }

ビューモデルで管理されるカスタム状態と、ロータリーのスクロール イベントの処理に使用されるカスタム コールバックを作成します。

// VolumeViewModel.kt

object VolumeRange(
    public val max: Int = 10
    public val min: Int = 0
)

val volumeState: MutableStateFlow<Int> = ...

fun onVolumeChangeByScroll(pixels: Float) {
    volumeState.value = when {
        pixels > 0 -> min (volumeState.value + 1, VolumeRange.max)
        pixels < 0 -> max (volumeState.value - 1, VolumeRange.min)
    }
}

わかりやすくするため、上記の例ではピクセル値を使用していますが、これは実際に使用すると、過度に敏感になる可能性が高くなります。

イベントを受け取ったら、次のスニペットに示すように、コールバックを使用します。

val focusRequester: FocusRequester = remember { FocusRequester() }
val volumeState by volumeViewModel.volumeState.collectAsState()

Column(
    modifier = Modifier
        .fillMaxSize()
        .onRotaryScrollEvent {
            volumeViewModel
                .onVolumeChangeByScroll(it.verticalScrollPixels)
            true
        }
        .focusRequester(focusRequester)
        .focusable(),
) { ... }