Compose によるロータリー入力

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

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

スクロール

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

Wear OS 向け Compose を使用してロータリー スクロールを実装します。この例は、縦方向にスクロールするスキャフォールドと ScalingLazyColumn を使用するアプリを示しています。スキャフォールドは Wear OS アプリの基本的なレイアウト構造を提供し、スクロール インジケーター用のスロットが用意されています。スクロールの進捗状況を表示するには、リスト状態オブジェクトに基づいて位置インジケーターを作成します。ScalingLazyColumn などのスクロール可能なビューには、ロータリー入力を追加するためのスクロール可能な状態がすでにあります。ロータリーのスクロール イベントを受け取る手順は次のとおりです。

  1. FocusRequester を使用して明示的にフォーカスをリクエストします。
  2. onRotaryScrollEvent 修飾子を追加して、ユーザーがリューズを回したときやベゼルを回転したときにシステムが生成したイベントをインターセプトします。各ロータリー イベントには設定されたピクセル値があり、垂直方向または水平方向にスクロールします。この修飾子には、イベントが消費されたかどうかを示すコールバックもあり、消費されると親へのイベント伝播を停止します。
val listState = rememberScalingLazyListState()

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

    val focusRequester = rememberActiveFocusRequester()
    val coroutineScope = rememberCoroutineScope()

    ScalingLazyColumn(
        modifier = Modifier
            .onRotaryScrollEvent {
                coroutineScope.launch {
                    listState.scrollBy(it.verticalScrollPixels)

                    listState.animateScrollBy(0f)
                }
                true
            }
            .focusRequester(focusRequester)
            .focusable(),
        state = listState
    ) { ... }
}

離散値

ロータリー インタラクションを使用して、個別の値も調整します。たとえば、設定で明るさを調整したり、アラームを設定するときに時間選択ツールで数字を選択したりできます。

ScalingLazyColumn と同様に、ピッカー、スライダー、ステッパー、その他のコンポーザブルには、ロータリー入力を受け取るためにフォーカスが必要です。時間選択ツールで時間や分など、スクロール可能な複数のターゲットが画面上にある場合は、ターゲットごとに FocusRequester を作成し、ユーザーが時間または分のいずれかをタップしたときにそれに応じてフォーカスの変更を処理します。

@Composable
fun TimePicker() {
    var selectedColumn by remember { mutableStateOf(0) }
    val focusRequester1 = remember { FocusRequester() }
    val focusRequester2 = remember { FocusRequester() }

    Row {
       Picker(...)
       Picker(...)
    }

    LaunchedEffect(selectedColumn) {
        listOf(focusRequester1,
               focusRequester2)[selectedColumn]
            .requestFocus()
    }
}

カスタム アクション

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

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

// 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(),
) { ... }

参考情報

Horologist の使用を検討してください。Horologist には、Compose for Wear OS やその他の Wear OS API が備える機能を補完する、一連の Wear ライブラリが用意されています。Horologist は高度なユースケースの実装を実現し、デバイス固有の多くの詳細情報を提供します。

たとえば、感度はロータリー入力ソースごとに異なる可能性があります。スムーズに値間を遷移するには、レート制限をするか、遷移用のスナップまたはアニメーションを追加します。これにより、ユーザーにとって回転速度がより自然になったように感じられます。Horologist には、スクロール可能なコンポーネントの修飾子と個別の値の修飾子が含まれています。また、フォーカスを処理するユーティリティや、ハプティクスを使用する音量コントロールを実装するオーディオ UI ライブラリも含まれています。

詳細については、GitHub の Horologist をご覧ください。