大画面のデバイスでは、キーボード、マウス、トラックパッド、タッチペン、ゲームパッドを使用してアプリを操作することが多くなります。アプリが外部デバイスからの入力を受け入れるようにするには、以下を行います。
- 基本的なキーボード サポートをテストする(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 要素へのアクセスをテストします。よく使用する要素には、マウスやタップ入力なしでアクセスできるようにする必要があります。
ユーザー補助機能を必要とするユーザーにとってはキーボードのサポートが不可欠であることに留意してください。
キーストローク
画面上の仮想キーボード(IME)で処理されるテキスト入力(TextFieldなど)の場合、アプリは追加の開発作業なしで、大画面デバイスで想定どおりに動作するはずです。フレームワークで予測できないキーストロークについては、アプリ側で動作を処理する必要があります。これは、特にカスタムビューを使用するアプリに当てはまります。
たとえば、Enter キーを使用してメッセージを送信するチャットアプリ、Space キーで再生を開始、停止するメディアアプリ、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() コールバックをオーバーライドして、受信したキーコードごとに想定される動作を追加することもできます。
kotlin
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>
アプリがシステムに登録されると、ユーザーはそれをデフォルトのメモ作成アプリとして選択できます。新しいメモがリクエストされると、アプリはタッチペン入力に対応した空のメモを作成する必要があります。ユーザーが画像(スクリーンショットやダウンロードした画像など)にアノテーションを付けることを希望すると、アプリが起動し、ClipData に content:// URI を持つ 1 つ以上のアイテムが含まれます。アプリは、最初に添付された画像を背景画像として使用するメモを作成し、ユーザーがタッチペンで画面に描画できるモードに入る必要があります。
タッチペンなしでメモ作成インテントをテストする
アプリがメモ作成インテントに正しく応答するかどうかを、アクティブなタッチペンがない状態でテストするには、次の方法で ChromeOS のメモ作成オプションを表示します。
- デベロッパー モードに切り替えて、デバイスを書き込み可能にする
- Ctrl+Alt+F2 キーを押してターミナルを開きます。
sudo vi /etc/chrome_dev.confコマンドを実行するiを押して編集し、ファイルの末尾の新しい行に--ash-enable-paletteを追加します。- Esc キーを押してから :、w、q を入力し、Enter キーを押して保存する
- Ctrl+Alt+F1 キーを押して、通常の ChromeOS UI に戻る
- ログアウトしてから再度ログインする
シェルフにタッチペン メニューが表示されます。
- シェルフ内のタッチペン ボタンをタップし、[新しいメモ] を選択します。これにより、空白の描画メモが開きます。
- スクリーンショットを撮ります。シェルフからタッチペン ボタン > [画面をキャプチャ] を選択するか、画像をダウンロードします。通知に [Annotate image] オプションが表示されます。これにより、画像にアノテーションを付けられる状態でアプリが起動します。
マウスとタッチパッドのサポート
ほとんどのアプリでは、一般的に、大画面中心のイベント(右クリック、ホバー、ドラッグ&ドロップ)のみを処理する必要があります。
右クリック
リストアイテムの長押しなど、アプリでコンテキスト メニューを表示するアクションは、右クリック イベントにも反応する必要があります。
右クリック イベントを処理するには、アプリで 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 — Implementing Drag & Drop のブログ投稿をご覧ください。
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" />