Jetpack Compose を使用して、キーボード、マウス、トラックパッド、タッチペンのサポートを追加する

1. はじめに

アプリが標準のスマートフォンで利用可能な場合、タブレット、折りたたみ式デバイス、ChromeOS デバイスなどの大画面デバイスでも利用できます。

ユーザーは小画面での UX と同等かそれ以上のユーザー エクスペリエンスを、アプリが大画面でも提供することを期待しています。

また、大画面デバイスの場合、ユーザーは物理キーボードやマウス、トラックパッドなどのポインティング デバイスでアプリを使用する可能性が高くなります。Chromebook などの一部の大画面デバイスには、物理キーボードとポインティング デバイスが搭載されています。それ以外のデバイスの場合は、USB または Bluetooth のキーボードとポインティング デバイスに接続します。ユーザーは物理キーボードとポインティング デバイスでアプリを使用しているときも、タッチ スクリーンでアプリを使用しているときと同じタスクを実行できることを求めています。

前提条件

  • Compose を使ってアプリを構築した経験
  • Kotlin に関する基本的な知識(ラムダやコルーチンなど)

構築内容

Jetpack Compose ベースのアプリに、物理キーボードとマウスのサポートを追加します。手順は次のとおりです。

  1. 大画面のアプリの品質に関するガイドラインで定義されている条件に基づきアプリを確認する
  2. 監査結果をレビューして、物理キーボードとマウスのサポートに関する問題を特定する
  3. 問題を修正する

具体的には、サンプルアプリを次の点でアップデートします。

  • キーボードの操作
  • 上下にスクロールするキーボード ショートカット
  • キーボード ショートカット ヘルパー

学習内容

  • 仮想デバイスのサポートに関してアプリを監査する方法
  • Compose でキーボード ナビゲーションを管理する方法
  • Compose でキーボード ショートカットを追加する方法

必要なもの

  • Android Studio Hedgehog 以降
  • サンプルアプリを実行する次のいずれかのデバイス:
  • 物理キーボードとマウスを搭載した大画面デバイス
  • プロファイルがデスクトップ デバイス定義カテゴリに属する Android 仮想デバイス

2. セットアップ

  1. large-screen-codelabs の GitHub リポジトリのクローンを作成します。
git clone https://github.com/android/large-screen-codelabs

または、large-screen-codelabs の ZIP ファイルをダウンロードしてアーカイブを解除します。

  1. add-keyboard-and-mouse-support-with-compose フォルダに移動します。
  2. Android Studio でプロジェクトを開きます。add-keyboard-and-cursor-support-with-compose フォルダには 1 つのプロジェクトが含まれています。
  3. Android タブレット、折りたたみ式デバイス、物理キーボードとマウスを搭載した ChromeOS デバイスがない場合は、Android Studio でデバイス マネージャーを開き、[Desktop] カテゴリで仮想デバイスを作成します。

[Desktop] カテゴリの仮想デバイス

3. アプリを使ってみる

サンプルアプリには記事のリストが表示されます。ユーザーはリストから選択した記事を閲覧できます。

アプリはアプリのウィンドウ幅に応じてレイアウトを適宜更新します。アプリのウィンドウ幅を分類するウィンドウ クラスには、コンパクト、中程度、拡大の 3 つがあります。

ウィンドウ幅のウィンドウ サイズクラス: コンパクト、中程度、拡大。アプリケーション ウィンドウが 600 dp 未満の場合、ウィンドウ幅はコンパクトに分類される。ウィンドウ幅が 640 dp 以上の場合は、拡大に分類される。ウィンドウがコンパクトにも拡大にも属さない場合、ウィンドウ サイズクラスは中程度となる。

コンパクトと中程度ウィンドウ サイズクラスのレイアウト

アプリはシングルペイン レイアウトを使用します。アプリのホーム画面に記事のリストが表示されます。ユーザーがリストから記事を選択すると、画面遷移が行われ、記事が表示されます。

グローバル ナビゲーションは、ナビゲーション ドロワーで実装されます。

アプリはコンパクト ウィンドウでデスクトップ エミュレータ上で実行されている。記事のリストが表示されている。

拡大ウィンドウ サイズクラスのレイアウト

このアプリはリストと詳細レイアウトを使用します。リストペインには記事のリストが表示されます。詳細ペインには選択した記事が表示されます。

グローバル ナビゲーションは、ナビゲーション レールで実装されます。

アプリは拡大ウィンドウ サイズクラスでデスクトップ エミュレータ上で実行されている。

4. 背景情報

Compose には、アプリが物理キーボードとマウスからのイベントを処理するうえで役立つ、さまざまな API が用意されています。一部の API では、タッチイベントの処理と同様のキーボード イベントとマウスイベントの処理が可能です。そのため、多くのユースケースで、デベロッパーによる開発の手間なく、アプリで物理キーボードとマウスをサポートできます。

典型的な例としては、クリックの検出を可能にする clickable 修飾子があります。指でのタップがクリックとして検出されます。マウスクリックと Enter キーの押下もクリックとして検出されます。アプリが clickable 修飾子でクリックを検出すると、入力デバイスに関係なく、アプリはユーザーがコンポーネントを操作できるようにします。

ただ、このハイレベルな API のサポートがあるものの、それでも物理キーボードとマウスのサポートには開発作業が必要となります。その理由は、アプリをテストして特殊なケースを把握する必要があるためです。また、次のようなデバイスの特性により発生するユーザーの負担を軽減する必要もあります。

  • ユーザーがクリックできるコンポーネントを特定できない
  • ユーザーが思うようにキーボード フォーカスを動かせない
  • ユーザーが物理キーボードを使用している場合上下にスクロールできない

キーボード フォーカス

キーボード フォーカスは、物理キーボードと画面タップのインタラクションにおける主な違いです。ユーザーは以前にタップしたコンポーネントの位置に関係なく、画面上の任意のコンポーネントをタップできます。一方、キーボードの場合、実際のインタラクションを開始する前に、操作するコンポーネントを選択する必要があります。この選択を「キーボード フォーカス」と呼びます。

ユーザーは Tab キーと方向キー(または矢印キー)を使用してキーボード フォーカスを移動できます。キーボード フォーカスは、デフォルトでは隣接するコンポーネントにのみ移動します

物理キーボードによる煩わしさのほとんどは、キーボードのフォーカスに関連しています。よくある問題としては次のものがあります。

  • 操作するコンポーネントにキーボード フォーカスを移動できない
  • Enter キーを押しても、コンポーネントがクリックを検知しない
  • キーボード フォーカスがユーザーの想定と異なる形で移動する
  • 画面遷移後、操作するコンポーネントにキーボード フォーカスを移動するには、数多くのキーを押さなければならない
  • キーボード フォーカスを示す目印がないため、どのコンポーネントにキーボード フォーカスがあるかを判断できない
  • 新しい画面に移動したときに、キーボード フォーカスがあるデフォルト コンポーネントを判断できない

キーボード フォーカスを視覚的に示すことは重要です。視覚的に示していないと、ユーザーはアプリでフォーカスを見つけられず、Enter キーを押したときに何が起こるかを把握できません。ハイライト表示は、キーボード フォーカスを示すうえでよく使われる視覚的な目印です。右側のカードにあるボタンにキーボード フォーカスがあることが、ハイライト表示されていることからわかります。

53ee7662b764f2dd.png

キーボード ショートカット

ユーザーは物理キーボードでアプリを使用しているときに、一般的なキーボード ショートカットを使用できることを求めています。一部のコンポーネントでは、デフォルトで標準のキーボード ショートカットが有効になっています。BasicTextField がその典型的な例です。これにより、ユーザーは次のような標準のテキスト編集キーボード ショートカットを使用できます。

ショートカット

機能

Ctrl+C

コピー

Ctrl+X

切り取り

Ctrl+V

貼り付け

Ctrl+Z

元に戻す

Ctrl+Y

やり直す

アプリでキーボード ショートカットを追加するには、キーイベントを処理します。onKeyEvent 修飾子と onPreviewKeyEvent 修飾子を使用すると、キーイベントをモニタリングできます。

ポインティング デバイス: マウス、トラックパッド、タッチペン

アプリはマウス、トラックパッド、タッチペンを同じ方法で処理します。トラックパッドをタップすると、clickable 修飾子によりクリックとして検知されます。タッチペンでタップした場合も、クリックとして検知されます。

コンポーネントをクリックできるかどうかについて、ユーザーが視覚的に理解できるようにすることが重要です。そのため、大画面アプリの品質に関するガイドラインではホバー状態について言及しています。

マテリアル 3 コンポーネントはデフォルトでホバー状態をサポートしています。マテリアル 3 にはホバー状態の視覚効果が備わっています。indication 修飾子を使用して、インタラクティブ コンポーネントに適用できます。

スクロール

スクロール可能なコンテナは、デフォルトでマウスホイールによるスクロール、トラックパッドでのスクロール ジェスチャー、Page up キーと Page down キーによるスクロールをサポートしています。

水平スクロールでは、ホバー状態で左右の矢印ボタンを表示するようにすると、ユーザーがそのボタンをクリックしてコンテンツをスクロールできるため、アプリが非常にユーザー フレンドリーになります。

17feb4d3bf08831e.png

デバイスの接続と切断による構成の変更

ユーザーはアプリの実行中にキーボードとマウスを接続したり、切断したりすることがあります。大量のテキストを入力するテキスト フィールドが表示された場合、ユーザーは物理キーボードを接続することがあります。Bluetooth 接続のマウスはスリープモードになると、接続が切断されます。USB 接続のキーボードが誤って外れることもあります。

周辺機器のハードウェアの接続または切断は、構成の変更をトリガーします。アプリは構成の変更中ずっと、状態を保持する必要があります。詳細については、UI の状態を保存するをご覧ください。

5. キーボードとマウスでサンプルアプリを確認する

物理キーボードとマウスのサポートの開発作業を開始するには、サンプルアプリを起動して、次の点を確認します。

  • ユーザーはキーボード フォーカスをすべてのインタラクティブ コンポーネントに移動できる
  • ユーザーは Enter キーでフォーカスされたコンポーネントを「クリック」できる
  • インタラクティブ コンポーネントは、キーボード フォーカスになると、そのことを示す
  • Tab キー、Shift+Tab キー、方向(矢印)キーを使用して、キーボード フォーカスをユーザーの想定どおりに(確立された規則に沿って)移動できる
  • インタラクティブ コンポーネントにはホバー状態がある
  • ユーザーはインタラクティブ コンポーネントをクリックできる
  • コンテキスト メニューは長押しまたはテキスト選択でコンテキスト メニューが表示されるコンポーネントなど、適切なコンポーネントを右クリック(セカンダリ コントロール クリック)すると表示される

この Codelab では、すべてのアイテムを 2 回確認する必要があります。1 回はシングルペイン レイアウト用、もう 1 回はリストと詳細レイアウト用に確認します。

この Codelab で修正する問題

問題が見つかり、この Codelab で次の点を修正します。

  • ユーザーは記事を下にスクロールできないため、物理キーボードのみでは記事全体を読めない
  • ユーザーは詳細ペインにキーボード フォーカスがあるかどうかを判断できない

6. ユーザーが詳細ペインで記事全体を読めるようにする

詳細ペインには選択した記事が表示されます。記事によっては長すぎて、スクロールせずには記事全体を読めません。ただ、ユーザーは物理キーボードのみでは記事を上下にスクロールできません。

4627289223e5cfbc.gif

LazyColumn などのスクロール可能なコンテナでは、ユーザーは Page down キーで下にスクロールできます。この問題の根本原因は、ユーザーがキーボード フォーカスを詳細ペインに移動できないことにあります。

コンポーネントはキーボード イベントを受信できるように、キーボード フォーカスできる必要があります。focusable 修飾子を使用すると、変更されたコンポーネントがキーボード フォーカスされるようになります。

この問題を解決するには次の手順に沿って操作します。

  1. ui/article/PostContent.kt ファイルでコンポーズ可能な関数 PostContent にアクセスします。
  2. focusable 修飾子を使用してコンポーズ可能な関数 LazyColumn を変更します。
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun PostContent(
    post: Post,
    modifier: Modifier = Modifier,
    contentPadding: PaddingValues = PaddingValues(0.dp),
    state: LazyListState = rememberLazyListState(),
    coroutineScope: CoroutineScope = rememberCoroutineScope(),
    focusRequester: FocusRequester = remember { FocusRequester() },
    header: (@Composable () -> Unit)? = null
) {
    LazyColumn(
        contentPadding = contentPadding,
        modifier = modifier
            .padding(horizontal = defaultSpacerSize)
            .focusable(),
        state = state,
    ) {
      // Code to layout the selected article.
    }
}

記事にキーボード フォーカスがあることを示す

これで、ユーザーは Page down キーで記事を下にスクロールして、記事全体を読めるようになりました。しかし、視覚効果がないため、PostContent コンポーネントにキーボード フォーカスがあるかどうかを判断するのは困難です。

Indication をコンポーネントに関連付けることにより、アプリでキーボード フォーカスを視覚的に示せます。Indication はインタラクションに応じて視覚効果をレンダリングするオブジェクトを作成します。たとえば、マテリアル 3 のデフォルトの Indication では、キーボード フォーカスがあるコンポーネントがハイライト表示されます。

サンプルアプリには、BorderIndication という Indication があります。これにより、キーボード フォーカスがあるコンポーネントの横に線が表示されます(次のスクリーンショットをご参照ください)。コードは ui/components/BorderIndication.kt ファイルに保存されます。

記事にキーボード フォーカスがあるとき、記事の横に薄いグレーの線が表示される。

PostConent コンポーザブルにキーボード フォーカスがあるときに BorderIndication を表示するには、次の手順に沿って操作します。

  1. ui/article/PostContent.kt ファイルでコンポーズ可能な関数 PostContent にアクセスします。
  2. remember() 関数の戻り値に関連付けられた interactionSource 値を宣言します。
  3. 作成された MutableInteractionSource オブジェクトが interactionSource 値に関連付けられるように、remember() 関数で MutableInteractionSource() 関数を呼び出します。
  4. interactionSource パラメータを使用して interactionSource 値を focusable 修飾子に渡します。
  5. indication 修飾子の呼び出しの後に focusable 修飾子を呼び出すように、PostContent コンポーザブルの修飾子を変更します。
  6. interactionSource 値と BorderIndication 関数の戻り値を Indication 修飾子に渡します。
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun PostContent(
    post: Post,
    modifier: Modifier = Modifier,
    contentPadding: PaddingValues = PaddingValues(0.dp),
    state: LazyListState = rememberLazyListState(),
    coroutineScope: CoroutineScope = rememberCoroutineScope(),
    focusRequester: FocusRequester = remember { FocusRequester() },
    header: (@Composable () -> Unit)? = null
) {
    val interactionSource = remember { MutableInteractionSource() }

    LazyColumn(
        contentPadding = contentPadding,
        modifier = modifier
            .padding(horizontal = defaultSpacerSize)
            .indication(interactionSource, BorderIndication())
            .focusable(interactionSource = interactionSource),
        state = state,
    ) {
      // Code to layout the selected article.
    }
}

上下にスクロールするキーボード ショートカットを追加する

ユーザーが Spacebar を使って上下にスクロールできるようにすることは一般的な機能です。アプリでこの機能を実装するには、次の表に示すようにキーボード ショートカットを追加します。

ショートカット

機能

Spacebar

記事を下にスクロール

Shift + Spacebar

記事を上にスクロール

onKeyEvent 修飾子を使用すると、アプリは変更されたコンポーネントで発生するキーイベントを処理できます。この修飾子は、キーイベントを記述する KeyEvent オブジェクトで呼び出されるラムダを受け取ります。ラムダはキーイベントが使用されたかどうかを示す Boolean 値を返します。

LazyColumnLazyRow のスクロール位置は LazyListState オブジェクトでキャプチャされます。アプリは LazyListState オブジェクトに対して animateScrollBy() の suspend メソッドを呼び出すことで、スクロールをトリガーできます。このメソッドは LazyColumn を指定されたピクセル数、下にスクロールします。負の浮動小数点値を指定して suspend 関数を呼び出すと、関数は LazyColumn を上にスクロールします。

このようなキーボード ショートカットを実装するには、次の手順に沿って操作します。

  1. ui/article/PostContent.kt ファイルでコンポーズ可能な関数 PostContent にアクセスします。
  2. onKeyEvent 修飾子を使用してコンポーズ可能な関数 LazyColumn を変更します。
  3. 次のように、onKeyEvent 修飾子に渡されるラムダに if 式を追加します。
  • 次の条件が満たされている場合、true を返す
  • Spacebar が押されている(type 属性が KeyType.KeyDown で、key 属性が Key.Spacebar であるかをテストすることで検出可能)
  • isCtrlPressed 属性は false であるため、Ctrl キーは押されていない
  • isAltPressed 属性は false であるため、Alt キーは押されていない
  • isMetaPressed 属性は false であるため、Meta キー(注を参照)は押されていない
  • それ以外の場合は false を返す
  1. 以下のようにして、Spacebar でスクロールの量を決定します。
  • Shift キーが押されたときは -0.4f で、これは所定の KeyEvent オブジェクトの isShiftPressed 属性で記述される
  • それ以外の場合は 0.4f
  1. コンポーズ可能な関数 PostContent のパラメータである coroutineScope に対して launch() メソッドを呼び出します。
  2. 先ほどのステップで算出した相対的なスクロール量と、launch メソッドのランダム パラメータの state.layoutInfo.viewportSize.height 属性を乗算して、実際のスクロール量を計算します。この属性はコンポーズ可能な関数 PostContent で呼び出される LazyColumn の高さを表します。
  3. launch() メソッドのラムダで state.animateScrollBy() メソッドを呼び出して、垂直スクロールをトリガーします。
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun PostContent(
    post: Post,
    modifier: Modifier = Modifier,
    contentPadding: PaddingValues = PaddingValues(0.dp),
    state: LazyListState = rememberLazyListState(),
    coroutineScope: CoroutineScope = rememberCoroutineScope(),
    focusRequester: FocusRequester = remember { FocusRequester() },
    header: (@Composable () -> Unit)? = null
) {
    val interactionSource = remember { MutableInteractionSource() }

    LazyColumn(
        contentPadding = contentPadding,
        modifier = modifier
            .padding(horizontal = defaultSpacerSize)
            .onKeyEvent {
                if (
                    it.type == KeyEventType.KeyDown &&
                    it.key == Key.Spacebar &&
                    !it.isCtrlPressed &&
                    !it.isAltPressed &&
                    !it.isMetaPressed
                ) {

                    val relativeAmount = if (it.isShiftPressed) {
                        -0.4f
                    } else {
                        0.4f
                    }
                    coroutineScope.launch {
                        state.animateScrollBy(relativeAmount * state.layoutInfo.viewportSize.height)
                    }
                    true
                } else {
                    false
                }
            }
            .indication(interactionSource, BorderIndication())
            .focusable(interactionSource = interactionSource),
        state = state,
    ) {
      // Code to layout the selected article.
    }
}

キーボード ショートカットをユーザーに知らせる

ユーザーがショートカットについて知らなければ、追加されたキーボードを十分に活用できません。アプリでは、Android システム UI の一部であるキーボード ショートカット ヘルパーを使用して、利用できるショートカットをユーザーに共有できます。ユーザーは Meta+/ でショートカット ヘルパーを開けます。

キーボード ショートカット ヘルパーに、前のセクションで追加したキーボード ショートカットが表示されている。

アプリのメイン アクティビティで onProvideKeyboardShortcuts() メソッドをオーバーライドして、キーボード ショートカット ヘルパーにキーボード ショートカットのリストを渡します。

具体的には、アプリは onProvideKeyboardShortcuts() に渡される可変リストに KeyboardShortcutGroup オブジェクトを追加することで、それらのオブジェクトを提供します。各 KeyboardShortcutGroup はキーボード ショートカットの名前が付いたカテゴリを表しています。これにより、アプリは利用できるキーボード ショートカットを目的やコンテキスト別にグループ化できます。

サンプルアプリには、SpacebarShift+Spacebar の 2 つのキーボード ショートカットがあります。

キーボード ショートカット ヘルパーにこの 2 つのショートカットが表示されるようにするには、次の手順を行います。

  1. MainActivity.kt ファイルを開きます。
  2. MainActivityonProvideKeyboardShortcuts() メソッドをオーバーライドします。
  3. キーボード ショートカット ヘルパーを使用できるように、Android SDK のバージョンが Android 7.0(API レベル 24)以降であることを確認します。
  4. メソッドの最初のパラメータが null ではないことを確認します。
  5. 次のパラメータを使用して、Spacebar キーの KeyboardShortcutInfo オブジェクトを作成します。
  • 説明テキスト
  • android.view.KeyEvent.KEYCODE_SPACE
  • 0(修飾子がないことを示す)
  1. 次のパラメータを使用して、Shift+Spacebar の別の KeyboardShortcutInfo を作成します。
  • 説明テキスト
  • android.view.KeyEvent.KEYCODE_SPACE
  • android.view.KeyEvent.META_SHIFT_ON
  1. 2 つの KeyboardShortcutInfo オブジェクトを含む不変リストを作成します。
  2. 次のパラメータを使用して KeyboardShortcutGroup オブジェクトを作成します。
  • グループ名(テキスト)
  • 前のステップの不変リスト
  1. onProvideKeyboardShortcuts() メソッドの最初のパラメータとして渡される可変リストに、KeyboardShortcutGroup オブジェクトを追加します。

オーバーライドされたメソッドは次のようになります。

   override fun onProvideKeyboardShortcuts(
        data: MutableList<KeyboardShortcutGroup>?,
        menu: Menu?,
        deviceId: Int
    ) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && data != null) {
            val shortcutGroup = KeyboardShortcutGroup(
                "To read articles",
                listOf(
                    KeyboardShortcutInfo("Scroll down", KeyEvent.KEYCODE_SPACE, 0), // 0 means no modifier key is pressed
                    KeyboardShortcutInfo("Scroll up", KeyEvent.KEYCODE_SPACE, KeyEvent.META_SHIFT_ON),
                )
            )
            data.add(shortcutGroup)
        }
    }

実行する

これで、ユーザーは Spacebar で記事をスクロールして、記事全体を読めるようになりました。スクロールできるかどうかを試すには、Tab キーまたは方向キーを使用して、キーボード フォーカスを記事に移動させます。Spacebar を押すよう促すメッセージが表示されます。

キーボード ショートカット ヘルパーに、追加した 2 つのキーボード ショートカットが表示されます(Meta+/ キーを押します)。追加したショートカットは [現在のアプリ] タブに表示されます。

7. 詳細ペインでのキーボード ナビゲーションを迅速化する

アプリが拡大ウィンドウ サイズクラスで実行されている場合、ユーザーは Tab キーを数回押して、キーボード フォーカスを詳細ペインに移動する必要があります。右方向キーを使用すると、ユーザーは 1 回の操作でキーボード フォーカスを記事リストから記事に移動できますが、それでもキーボード フォーカスを移動する必要はあります。初期フォーカスは、記事を読むというユーザーの主な目的をサポートしていません。

アプリで FocusRequester オブジェクトを使用することによって、特定のコンポーネントにキーボード フォーカスを移動するようリクエストできます。focusRequester 修飾子は、変更されたコンポーネントに FocusRequester オブジェクトを関連付けます。アプリは FocusRequester オブジェクトの requestFocus() メソッドを呼び出すことにより、フォーカスを移動する実際のリクエストを送信できます。

キーボード フォーカスの移動リクエストの送信は、コンポーネントの副次的な結果です。アプリは LaunchedEffect 関数を使用して、適切な方法でメソッドを呼び出す必要があります。

ユーザーが記事リストから記事を選択したときに PostContent コンポーザブルがキーボード フォーカス状態になるよう設定する手順は次のとおりです。

  1. ui/article/ PostContent.kt ファイルでコンポーズ可能な関数 PostContent にアクセスします。
  2. focusRequester 修飾子を使用して、focusRequester 値をコンポーズ可能な関数 LazyColumn に関連付けます。focusRequester 値はコンポーズ可能な関数 PostContent のオプション パラメータとして指定されます。
  3. コンポーズ可能な関数 PostContent の最初のパラメータである post を指定して LaunchedEffect を呼び出し、ユーザーが記事を選択したときに渡されたラムダが呼び出されるようにします。
  4. LaunchedEffect 関数に渡されるラムダの focusRequester.requestFocus() メソッドを呼び出します。

更新された PostContent コンポーザブルは次のようになります。

@OptIn(ExperimentalFoundationApi::class)
@Composable
fun PostContent(
    post: Post,
    modifier: Modifier = Modifier,
    contentPadding: PaddingValues = PaddingValues(0.dp),
    state: LazyListState = rememberLazyListState(),
    coroutineScope: CoroutineScope = rememberCoroutineScope(),
    focusRequester: FocusRequester = remember { FocusRequester() },
    header: (@Composable () -> Unit)? = null
) {
    val interactionSource = remember { MutableInteractionSource() }

    LaunchedEffect(post) {
        focusRequester.requestFocus()
    }

    LazyColumn(
        contentPadding = contentPadding,
        modifier = modifier
            .padding(horizontal = defaultSpacerSize)
            .onKeyEvent {
                if (it.type == KeyEventType.KeyDown && it.key == Key.Spacebar) {
                    val relativeAmount = if (it.isShiftPressed) {
                        -0.4f
                    } else {
                        0.4f
                    }
                    coroutineScope.launch {
                        state.animateScrollBy(relativeAmount * state.layoutInfo.viewportSize.height)
                    }
                    true
                } else {
                    false
                }
            }
            .focusRequester(focusRequester),
            .indication(interactionSource, BorderIndication())
            .focusable(interactionSource = interactionSource),
        state = state,
    ) {
      // Code to layout the selected article.
    }
}

実行する

これでユーザーが記事リストから記事を選択すると、キーボード フォーカスが記事に移動するようになりました。記事を選択すると、Spacebar を使用して記事を下にスクロールするよう促すメッセージが表示されます。

8. 完了

おめでとうございます。物理キーボードとマウスのサポートをサンプルアプリに追加できました。これにより、ユーザーは物理キーボードまたはマウスのみを使用して、記事リストから記事を選択し、選択した記事を読めるようになります。

物理キーボードとマウスのサポートを追加するうえで必要な次のことを学習しました。

  • アプリが物理キーボードとマウスをサポートしているかどうかを確認する方法(エミュレータを使った場合を含む)
  • Compose でキーボード ナビゲーションを管理する方法
  • Compose でキーボード ショートカットを追加する方法

また、若干のコード変更を行って、物理キーボードとマウスのサポートを追加しました。

これで、Compose を使用して、製品版アプリに物理キーボードとマウスのサポートを追加できるようになりました。

もう少し学習することにより、次の機能のキーボード ショートカットを追加できるようになります。

  • 選択した記事に高評価を付ける
  • 選択した記事をブックマークする
  • 選択した記事を他のアプリで共有する

詳細