大画面のデバイスでは、キーボード、マウス、トラックパッド、タッチペン、ゲームパッドを使用してアプリを操作することが多くなります。アプリが外部デバイスからの入力を受け入れるようにするには、以下を行います。
- 基本的なキーボード サポートをテストする(元に戻すための Ctrl+Z、コピーするための Ctrl+C、保存するための Ctrl+S など)。デフォルトのキーボード ショートカットの一覧については、キーボード アクションを処理するをご覧ください。
- 高度なキーボード サポートをテストする(Tab キーと矢印キーによるキーボード ナビゲーション、Enter キーによるテキスト入力の確定、メディアアプリでの Space キーによる再生と一時停止など)。
- 基本的なマウス操作をテストする(右クリックしてコンテキスト メニューを表示する、ユーザーがカーソルを合わせたときにアイコンを変更する、マウスホイールまたはトラックパッドでカスタム コンポーネントをスクロールするなど)。
- アプリ固有の入力デバイスをテストする(タッチペン、ゲーム コントローラ、音楽アプリの MIDI コントローラなど)。
- パソコン環境でアプリの魅力を高める高度な入力のサポートを検討する(DJ アプリ用のクロスフェーダーとしてのタッチパッド、ゲーム用のマウス キャプチャ、キーボードを多用するユーザー向けのキーボード ショートカットなど)。
キーボード
キーボード入力に対してアプリがどのように応答するかは、大画面でのユーザー エクスペリエンスを高める鍵になります。キーボード入力には、ナビゲーション、キーストローク、ショートカットの 3 種類があります。
ナビゲーション
タップ中心のアプリにキーボード ナビゲーションが実装されることはまれですが、ユーザーはアプリの使用時にキーボードがあれば、キーボード ナビゲーションを期待します。スマートフォン、タブレット、折りたたみ式デバイス、パソコンでユーザー補助を必要とするユーザーにとって、キーボード ナビゲーションは不可欠です。
多くのアプリでは、矢印キーと Tab キーによるナビゲーションは Android フレームワークによって自動的に処理されます。たとえば、Button
や clickable
修飾子を持つコンポーザブルなど、一部のコンポーザブルはデフォルトでフォーカス可能であり、キーボード ナビゲーションは一般的に追加のコードなしで機能します。デフォルトではフォーカス可能でないカスタム コンポーザブルのキーボード ナビゲーションを有効にするには、focusable
修飾子を追加します。
var color by remember { mutableStateOf(Green) } Box( Modifier .background(color) .onFocusChanged { color = if (it.isFocused) Blue else Green } .focusable() ) { Text("Focusable 1") }
詳細については、コンポーザブルをフォーカス可能にするをご覧ください。
フォーカスが有効になると、Android フレームワークはすべてのフォーカス可能なコンポーネントのナビゲーション マッピングを、位置に基づいて作成します。通常、これは想定どおりに機能し、特別な開発は不要です。
ただし、タブやリストなどの複雑なコンポーザブルの場合、Compose がタブナビゲーションの正しい次のアイテムを常に決定するとは限りません。たとえば、コンポーザブルの 1 つが完全に表示されない水平スクロール可能なものである場合などです。
フォーカスの動作を制御するには、コンポーザブルのコレクションの親コンポーザブルに focusGroup
修飾子を追加します。フォーカスはグループに移動し、グループ内を移動してから、次のフォーカス可能なコンポーネントに移動します。次に例を示します。
Row {
Column(Modifier.focusGroup()) {
Button({}) { Text("Row1 Col1") }
Button({}) { Text("Row2 Col1") }
Button({}) { Text("Row3 Col1") }
}
Column(Modifier.focusGroup()) {
Button({}) { Text("Row1 Col2") }
Button({}) { Text("Row2 Col2") }
Button({}) { Text("Row3 Col2") }
}
}
詳しくは、フォーカス グループを使用して一貫したナビゲーションを提供するをご覧ください。
キーボードのみを使用して、アプリのすべての UI 要素へのアクセスをテストします。よく使用する要素は、マウス入力やタップ入力なしでアクセスできるようにする必要があります。
ユーザー補助機能を必要とするユーザーにとってはキーボードのサポートが不可欠であることに留意してください。
キーストローク
、TextField
、などの画面上の仮想キーボード(IME)によって処理されるテキスト入力の場合、アプリは、デベロッパーによる追加の開発作業がなくても大画面デバイスで想定どおりに動作します。フレームワークが予測できないキーストロークについては、アプリが独自に動作を処理する必要があります。これは、特にカスタムビューを使用するアプリに当てはまります。
たとえば、Enter キーを使用してメッセージを送信するチャットアプリ、Spacebar で再生を開始および停止するメディアアプリ、W、A、S、D キーで移動を制御するゲームなどが挙げられます。
個々のキー入力は onKeyEvent
修飾子で処理できます。この修飾子は、変更されたコンポーネントがキーイベントを受信したときに呼び出されるラムダを受け取ります。KeyEvent#type
プロパティを使用すると、イベントがキーの押下(KeyDown
)かキーの離脱(KeyUp
)かを判断できます。
Box(
modifier = Modifier.focusable().onKeyEvent {
if(
it.type == KeyEventType.KeyUp &&
it.key == Key.S
) {
doSomething()
true
} else {
false
}
}
) {
Text("Press S key")
}
または、onKeyUp()
コールバックをオーバーライドし、受け取ったキーコードごとに期待される動作を追加することもできます。
override fun onKeyUp(keyCode: Int, event: KeyEvent): Boolean { return when (keyCode) { KeyEvent.KEYCODE_ENTER -> { sendChatMessage() true } KeyEvent.KEYCODE_SPACE -> { playOrPauseMedia() true } else -> super.onKeyUp(keyCode, event) } }
onKeyUp
イベントは、キーが離されたときに発生します。コールバックを使用すると、キーを長押ししたりゆっくり離したりした場合に、アプリが複数の onKeyDown
イベントを処理する必要がなくなります。キーが押された瞬間や、ユーザーがキーを長押ししているかどうかを検出する必要があるゲームやアプリは、onKeyDown
イベントをリッスンし、繰り返される onKeyDown
イベントを独自に処理できます。
詳しくは、キーボード アクションの処理をご覧ください。
ショートカット
ハードウェア キーボードを使用する際は、Ctrl キー、Alt キー、Shift キー、Meta キーを含む一般的なキーボード ショートカットが求められます。アプリにショートカットが実装されていないと、ユーザーがフラストレーションを感じる可能性があります。また上級ユーザーにとっては、よく使うアプリ固有のタスクのショートカットが役立ちます。ショートカットを実装することで、アプリの使いやすさを高め、ショートカットを備えていないアプリとの差別化を図ることができます。
一般的なショートカットには、Ctrl+S(保存)、Ctrl+Z(元に戻す)、Ctrl+Shift+Z(やり直し)などがあります。デフォルトのショートカットの一覧については、キーボード アクションの処理をご覧ください。
KeyEvent
オブジェクトには、修飾キーが押されているかどうかを示す次の属性があります。
例:
Box(
Modifier.onKeyEvent {
if (it.isAltPressed && it.key == Key.A) {
println("Alt + A is pressed")
true
} else {
false
}
}
.focusable()
)
詳しくは次の記事をご覧ください。
タッチペン
多くの大画面デバイスにはタッチペンが付属しています。Android アプリは、タッチペンをタッチスクリーン入力として処理します。Wacom Intuos のように、USB または Bluetooth の描画テーブルを備えたデバイスもあります。Android アプリは Bluetooth 入力を受け取れますが、USB 入力は受けられません。
タッチペンの MotionEvent
オブジェクトにアクセスするには、描画サーフェスに pointerInteropFilter
修飾子を追加します。モーション イベントを処理するメソッドを使用して ViewModel
クラスを実装します。このメソッドを、pointerInteropFilter
修飾子の onTouchEvent
ラムダとして渡します。
@Composable
@OptIn(ExperimentalComposeUiApi::class)
fun DrawArea(modifier: Modifier = Modifier) {
Canvas(modifier = modifier
.clipToBounds()
.pointerInteropFilter {
viewModel.processMotionEvent(it)
}
) {
// Drawing code here.
}
}
MotionEvent
オブジェクトには、イベントに関する情報が含まれます。
MotionEvent#getToolType()
は、ディスプレイに接触したツールに応じてTOOL_TYPE_FINGER
、TOOL_TYPE_STYLUS
、またはTOOL_TYPE_ERASER
を返します。MotionEvent#getPressure()
はタッチペンに加えられた物理的な圧力をレポートします(サポートされている場合)MotionEvent#getAxisValue()
をMotionEvent.AXIS_TILT
、MotionEvent.AXIS_ORIENTATION
と併用すると、タッチペンの物理的な傾きと向きを取得できます(サポートされている場合)
履歴ポイント
Android は、入力イベントを一括でまとめてフレームごとに 1 回送信します。タッチペン入力では、ディスプレイよりもはるかに高い頻度でイベントが報告される可能性があります。描画アプリを作成する場合は、次の getHistorical
API を使用して、最近発生した可能性があるイベントをチェックします。
MotionEvent#getHistoricalX()
MotionEvent#getHistoricalY()
MotionEvent#getHistoricalPressure()
MotionEvent#getHistoricalAxisValue()
パーム リジェクション
ユーザーがタッチペンを使用して描画、書き込み、またはアプリの操作を行うと、手のひらが画面に触れることがあります。タッチイベント(ACTION_DOWN
または ACTION_POINTER_DOWN
に設定)が、システムに認識される前にアプリに報告される場合、手のひらでの意図しないタッチは無視されます。
Android は、MotionEvent
をディスパッチすることにより、手のひらでのタッチイベントをキャンセルします。アプリが ACTION_CANCEL
を受け取った場合、操作はキャンセルされます。アプリが ACTION_POINTER_UP
を受け取った場合は、FLAG_CANCELED
が設定されているかどうかを確認します。設定されている場合は、操作をキャンセルします。
FLAG_CANCELED
のみの確認はしないでください。Android 13(API レベル 33)以降では、ACTION_CANCEL
イベントに FLAG_CANCELED
が設定されますが、それより前の Android バージョンではフラグは設定されません。
Android 12
Android 12(API レベル 32)以前の場合、パーム リジェクションの検出は、シングル ポインタ タッチイベントでのみ可能です。手のひらでのタッチが唯一のポインタである場合、モーション イベント オブジェクトに ACTION_CANCEL
が設定され、イベントがキャンセルされます。他のポインタがダウンしている場合、システムは ACTION_POINTER_UP
を設定しますが、パーム リジェクション検出には不十分です。
Android 13
Android 13(API レベル 33)以降では、手のひらでのタッチが唯一のポインタである場合、モーション イベント オブジェクトに ACTION_CANCEL
と FLAG_CANCELED
が設定され、イベントがキャンセルされます。他のポインタがダウンしている場合、ACTION_POINTER_UP
と FLAG_CANCELED
が設定されます。
アプリが ACTION_POINTER_UP
でモーション イベントを受け取った場合は必ず FLAG_CANCELED
をチェックして、イベントがパーム リジェクション(または他のイベントのキャンセル)を示しているかどうかを確認します。
メモ作成アプリ
ChromeOS には、登録されたメモ作成アプリをユーザーに表示するための特別なインテントがあります。アプリをメモ作成アプリとして登録するには、アプリ マニフェストに次の行を追加します。
<intent-filter>
<action android:name="org.chromium.arc.intent.action.CREATE_NOTE" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
アプリがシステムに登録されると、ユーザーはそのアプリをデフォルトのメモ作成アプリとして選択できます。新しいメモがリクエストされた場合、アプリはタッチペン入力が可能な空のメモを作成する必要があります。ユーザーが画像(スクリーンショットやダウンロードした画像など)にアノテーションを付けようとした場合、アプリは content://
URI を持つアイテムを 1 つ以上含む ClipData
とともに起動します。アプリは、最初の添付画像を背景画像として使用するメモを作成し、ユーザーがタッチペンで描画できるモードに移行する必要があります。
タッチペンなしでメモ作成インテントをテストする
[TBD セクションを削除]
アプリがメモ作成インテントに正しく応答するかどうかを、アクティブなタッチペンがない状態でテストするには、次の方法で ChromeOS のメモ作成オプションを表示します。
- デベロッパー モードに切り替えて、デバイスを書き込み可能にする
- Ctrl+Alt+F2 キーを押してターミナルを開きます。
sudo vi /etc/chrome_dev.conf
コマンドを実行するi
を押して入力モードに切り替え、ファイルの末尾に新しい行として--ash-enable-palette
を追加する- Esc キーを押してから :、w、q を入力し、Enter キーを押して保存する
- Ctrl+Alt+F1 キーを押して、通常の ChromeOS UI に戻る
- ログアウトしてから再度ログインする
シェルフにタッチペン メニューが表示されます。
- シェルフ内のタッチペン ボタンをタップし、[新しいメモ] を選択します。これにより、空白の描画メモが開きます。
- スクリーンショットを撮ります。シェルフからタッチペン ボタン > [画面をキャプチャ] を選択するか、画像をダウンロードします。通知に [画像にアノテーションを付ける] オプションが表示されます。これにより、画像にアノテーションを付けられる状態でアプリが起動します。
マウスとタッチパッドのサポート
一般的にほとんどのアプリは、右クリック、マウスオーバー、ドラッグ&ドロップという 3 つの大画面中心のイベントを処理するだけで済みます。
右クリック
リストアイテムの長押しなど、アプリにコンテキスト メニューを表示させるためのアクションはすべて、右クリック イベントにも反応する必要があります。
アプリが右クリック イベントを処理するには、View.OnContextClickListener
を登録する必要があります。
Box(modifier = Modifier.fillMaxSize()) {
AndroidView(
modifier = Modifier.fillMaxSize(),
factory = { context ->
val rootView = FrameLayout(context)
val onContextClickListener =
View.OnContextClickListener { view ->
showContextMenu()
true
}
rootView.setOnContextClickListener(onContextClickListener)
rootView
},
)
}
コンテキスト メニューの作成の詳細については、コンテキスト メニューを作成するをご覧ください。
カーソルを合わせる
マウスオーバー イベントを適切に処理することで、アプリのレイアウトが洗練されていて使いやすいとユーザーに感じさせることができます。これは、カスタムコンポーネントに特に当てはまります。
最も一般的な例は次の 2 つです。
- マウスポインタ アイコンを変更することで、要素にインタラクティブな動作がある(たとえば、クリック可能または編集可能である)かどうかをユーザーに知らせる
- 大規模なリストまたはグリッド内のアイテムにユーザーがカーソルを合わせたとき、そのアイテムに視覚的なフィードバックを追加する
ドラッグ&ドロップ
マルチウィンドウ環境では、ユーザーはアプリ間でアイテムをドラッグ&ドロップできることを期待します。これは、デスクトップ デバイス、タブレット、スマートフォン、分割画面モードの折りたたみ式デバイスに当てはまります。
ユーザーがアプリにアイテムをドラッグする可能性があるかどうかを考慮してください。たとえば、写真エディタは写真を受け取る必要があります。オーディオ プレーヤーは音声ファイルを受け取る必要があります。描画プログラムは写真を受け取る必要があります。
ドラッグ&ドロップのサポートを追加するには、 ドラッグ&ドロップ 、Android on ChromeOS - ドラッグ&ドロップの実装に関するブログ投稿をご覧ください。
ChromeOS 向けの特別な考慮事項
- アプリ外からドラッグしたアイテムにアクセスするには、
requestDragAndDropPermissions()
を使用して権限をリクエストします。 他のアプリにアイテムをドラッグするには、アイテムに
View.DRAG_FLAG_GLOBAL
フラグを設定する必要があります。ドラッグ イベントを開始するをご覧ください。
高度なポインタのサポート
マウス入力とタッチパッド入力の高度な処理を行うアプリは、
pointerInput
修飾子を実装して PointerEvent
を取得する必要があります。
@Composable private fun LogPointerEvents(filter: PointerEventType? = null) { var log by remember { mutableStateOf("") } Column { Text(log) Box( Modifier .size(100.dp) .background(Color.Red) .pointerInput(filter) { awaitPointerEventScope { while (true) { val event = awaitPointerEvent() // handle pointer event if (filter == null || event.type == filter) { log = "${event.type}, ${event.changes.first().position}" } } } } ) } }
PointerEvent
オブジェクトを調べて、次の情報を確認します。
PointerType
:PointerEvent#changes
からのマウス、タッチペン、タッチなどPointerEventType
: ポインタのアクション(押す、移動、スクロール、離すなど)
ゲーム コントローラ
大画面の Android デバイスの中には、最大 4 つのゲーム コントローラをサポートするものもあります。標準の Android ゲーム コントローラ API を使用してゲーム コントローラを処理します(ゲーム コントローラをサポートするをご覧ください)。
ゲーム コントローラのボタンは、一般的なマッピングに従って一般的な値にマッピングされます。ただし、すべてのゲーム コントローラ メーカーが同じマッピング規則に従っているわけではありません。各種の一般的なコントローラ マッピングをユーザーが選択できるようにすると、エクスペリエンスを大幅に向上させることができます。詳しくは、ゲームパッド ボタンの押下を処理するをご覧ください。
入力変換モード
ChromeOS では、デフォルトで入力変換モードが有効になっています。ほとんどの Android アプリの場合、このモードはパソコン環境で想定されるとおりに動作します。たとえば、タッチパッドでの 2 本指スクロール、マウスホイールによるスクロール、未加工のディスプレイ座標のウィンドウ座標へのマッピングなどが自動的に有効になります。通常、アプリ デベロッパーはこうした動作を実装する必要はありません。
カスタムの入力動作をアプリに実装する(たとえば 2 本の指でタッチパッドをつまむカスタム操作を定義する)場合や、アプリが想定する入力イベントが入力変換によって提供されない場合は、Android マニフェストに次のタグを追加することにより、入力変換モードを無効にできます。
<uses-feature
android:name="android.hardware.type.pc"
android:required="false" />