Compose での片手操作ジェスチャー


Wear OS 7(API レベル 37)以降では、片手操作ジェスチャー フレームワークと、Compose for Wear OS の一部である API を使用して、ユーザーがアプリをタッチレスで操作できるようになります。

当初は Google Pixel Watch デバイス(Google Pixel Watch 3 以降)でサポートされますが、このフレームワークはすべての OEM で利用できます。この API を採用することで、ハードウェア サポートの拡大に合わせて、アプリのジェスチャー サポートがエコシステム全体に自動的に拡大されます。

Wear OS フレームワークは、UI を煩雑にすることなく利用可能なジェスチャーをユーザーが発見できるように、ジェスチャー インジケーターのアニメーションを提供します。これらの視覚的なヒントは、ジェスチャーを実行できる場所をハイライト表示します。システムは、ユーザー設定に応じて、表示の頻度とミュートの頻度を自動的に管理します。

サポートされているジェスチャーとアクション

Wear OS ジェスチャー フレームワークは、次の 2 種類のジェスチャーをサポートしています。

  • メイン アクション(ダブルピンチ): 通話への応答やメディア再生の切り替えなど、画面上のメイン アクションにマッピングされます。
  • 閉じるアクション(手首を回す): 後方ナビゲーション、ダイアログの閉じる、プロンプトのキャンセルにマッピングされます。

Compose でジェスチャーを設定する

片手操作ジェスチャー API は UI を強化できますが、一部のハードウェアや OEM ではこれらのジェスチャーがサポートされていないことに留意してください。API が、アプリがサポートされていないデバイスのいずれかで実行されていることを検出すると、ライブラリは標準のタッチ操作に影響を与えることなく自動的に no-op になります。

標準の Compose の動作と同様に、修飾子を使用して UI 要素で片手操作を有効にします。アプリのジェスチャーは、実行するアクション(プライマリまたは閉じる)と、ヒントの表示頻度やミュートの頻度など、システムレベルのユーザー設定と連携する gestureId に従って構成します。この構成は、OneHandedGestureConfiguration オブジェクトを作成して表現します。これを作成するには、rememberOneHandedGestureConfiguration 関数を使用することをおすすめします。OneHandedGestureConfiguration では、ジェスチャーの優先度を指定することもできます。

rememberOneHandedGestureConfiguration 関数は、アプリの状態を公開することなく、再コンポーズ全体でユーザー インタラクションの履歴を追跡します。アプリで構成を作成したら、インタラクティブなコンポーザブルの Modifier.oneHandedGesture に構成を渡す必要があります。

利用可能なジェスチャーをユーザーが見つけられるように、ライブラリには OneHandedGestureClickIndicator メソッドが用意されています。このメソッドは、基盤となるコンテンツを置き換えて、ジェスチャー操作が利用可能であることをユーザーに知らせるラッパーとして機能します。

インタラクティブなコンポーネント

ボタンなどのインタラクティブなコントロールでジェスチャーを有効にするには、OneHandedGestureAction.Primary を指定する構成を作成し、oneHandedGesture 修飾子を適用します。同じ MutableInteractionSource をコントロールと修飾子の両方に渡して、ジェスチャー イベントがコントロールに視覚的な押下フィードバックを送信するようにします。

ジェスチャー インジケーターを有効にするには、OneHandedGestureClickIndicatorState のインスタンスを作成して保存します。次に、視覚的なフィードバックをトリガーするには、oneHandedGesture 修飾子によって提供される onGestureAvailable コールバック内で showIndicator を呼び出します。これにより、インジケータ イベントが発生したことがシステムに通知されます。呼び出されると、コンポーネントは通常のコンテンツをジェスチャー アニメーションに一時的に置き換えます。

var isPlaying by remember { mutableStateOf(false) }
val onClick = { isPlaying = !isPlaying }

val gestureConfig = rememberOneHandedGestureConfiguration(
    action = OneHandedGestureAction.Primary
)
val indicatorState = remember { OneHandedGestureClickIndicatorState() }
val coroutineScope = rememberCoroutineScope()
val interactionSource = remember { MutableInteractionSource() }

Button(
    onClick = onClick,
    interactionSource = interactionSource,
    modifier = Modifier
        .fillMaxWidth()
        .oneHandedGesture(
            gestureConfiguration = gestureConfig,
            interactionSource = interactionSource,
            onGestureLabel = if (isPlaying) "pause" else "play",
            onGestureAvailable = { coroutineScope.launch { indicatorState.showIndicator() } },
            onGesture = onClick
        )
) {
    OneHandedGestureClickIndicator(
        gestureConfiguration = gestureConfig,
        state = indicatorState
    ) {
        Text(if (isPlaying) "Pause" else "Play", modifier = Modifier.fillMaxWidth())
    }
}

スクロール可能なコンテナ

スクロール可能な画面やリストの場合は、OneHandedGestureAction.Primary を指定する構成を作成し、oneHandedGesture 修飾子をコンテナに適用して、scrollDown などのスクロール ヘルパーを呼び出します。

スクロール アクションの視覚的なフィードバックを提供するには、OneHandedGestureScrollIndicator を使用します。このコンポーネントは、スクロール位置を示す標準のスクロール インジケーターとして機能しますが、スクロール ジェスチャーがユーザーに利用可能であることを示すこともできます。このインジケーターは通常、ScreenScaffoldscrollIndicator スロットに渡され、TransformingLazyColumn などのスクロール可能なコンテナの状態と組み合わされます。また、OneHandedGestureScrollIndicatorState を監視して、視覚的なトランジションを管理します。

視覚的なフィードバックをトリガーするには、この状態に対して showIndicator を呼び出します。通常は、oneHandedGesture 修飾子の onGestureAvailable コールバック内で呼び出します。トリガーされると、インジケーターは標準の視覚状態を一時的にジェスチャー アニメーション シーケンスに置き換えて、ユーザーに警告します。

val scrollState = rememberTransformingLazyColumnState()
val gestureConfig = rememberOneHandedGestureConfiguration(
    action = OneHandedGestureAction.Primary,
    priority = OneHandedGesturePriority.Scrollable
)
val indicatorState = remember(gestureConfig) { OneHandedGestureScrollIndicatorState() }
val coroutineScope = rememberCoroutineScope()

ScreenScaffold(
    scrollState = scrollState,
    scrollIndicator = {
        OneHandedGestureScrollIndicator(
            gestureConfiguration = gestureConfig,
            indicatorState = indicatorState,
            scrollState = scrollState,
            modifier = Modifier.align(Alignment.CenterEnd)
        )
    }
) { contentPadding ->
    TransformingLazyColumn(
        state = scrollState,
        contentPadding = contentPadding,
        modifier = Modifier
            .fillMaxSize()
            .oneHandedGesture(
                gestureConfiguration = gestureConfig,
                onGestureLabel = "scroll",
                onGestureAvailable = {
                    coroutineScope.launch { indicatorState.showIndicator() }
                },
                onGesture = { OneHandedGestureDefaults.scrollDown(scrollState) }
            )
    ) {
        items(10) { index ->
            Text("Item $index", modifier = Modifier.padding(8.dp))
        }
    }
}

複数のジェスチャーを組み合わせる

OneHandedGestureConfiguration オブジェクトに gesturePriority を追加すると、スクロール ジェスチャーとクリック ジェスチャーの両方を同じプライマリ アクションで構成できます。

  • OneHandedGesturePriority.Clickable(最高): タイプ ButtonCard などのインタラクティブ コントロールに割り当て、画面に表示されたときにジェスチャーをキャプチャできるようにします。
  • OneHandedGesturePriority.Scrollable(中): スクロール可能またはページング可能なコンテナに割り当てます。クリック可能な子に譲りますが、クリック可能なコントロールが表示されていない場合はスクロールします。
  • OneHandedGesturePriority.Unspecified(最も低い): 優先度が割り当てられていない状態。priority が設定されていないジェスチャーのデフォルト値です。

内部ボタンに priority = OneHandedGesturePriority.Clickable を、親リストに priority = OneHandedGesturePriority.Scrollable を明示的に設定することで、システムはこのジェスチャー優先度の動作を表示できます。ユーザーが片手操作でプライマリ アクションをトリガーすると、まずボタンが表示されるまでリストが下にスクロールされます。次に、ボタンのクリック アクションをキャプチャします。

ADB を使用してジェスチャーをテストしてデバッグする

Android Debug Bridge(adb)と IWearGestureService システム サービスを使用すると、手首を動かすことなく、実機またはエミュレータで片手操作のジェスチャーをテストできます。

ジェスチャー シミュレーションを有効にする

  1. Wear OS デバイスに Wear OS 7(API レベル 37)以降が搭載されていることを確認します。
  2. 手首に装着していない、または充電器に接続していない実機でテストする場合は、デバイスがアクティブな状態を維持するように、オフボディ センサーの状態をオーバーライドします。
adb shell cmd sensorservice set-off-body-state 0

ADB を使用してジェスチャー イベントをトリガーする

ダブルピンチ ジェスチャー(Google Pixel Watch の Primary アクション)をシミュレートするには、次の ADB シェルコマンドを実行します。

adb shell cmd IWearGestureService gesture 1

手首を回すジェスチャー(Google Pixel Watch の Dismiss アクション)をシミュレートするには、次の ADB シェルコマンドを実行します。

adb shell cmd IWearGestureService gesture 2

ジェスチャー ヒントのトラッキングをリセット

システムはユーザー操作の履歴を追跡し、グローバル ケイデンス設定([常に] や [毎日] など)に基づいてフローティング ジェスチャーのヒントを表示します。アプリのジェスチャー インジケーターをデバッグするときは、このトラッキング履歴をリセットして、パッケージのヒントが再び表示されるようにします。

adb shell cmd IWearGestureService hint clear <your_package_name>

テストが完了したら、オフボディ センサーの状態をリセットします。

adb shell cmd sensorservice reset-off-body-state

参考情報

片手操作のジェスチャーをいつ、どこで使用するかについての設計ガイダンスについては、片手操作のジェスチャーをご覧ください。