大画面での入力の互換性

大画面のデバイスでは、キーボード、マウス、トラックパッド、タッチペン、ゲームパッドを使用してアプリを操作することが多くなります。アプリが外部デバイスからの入力を受け入れるようにするには、以下を行います。

  • 基本的なキーボード サポートをテストする(Ctrl+Z キーで元に戻す、Ctrl+C キーでコピー、Ctrl+S キーで保存するなど)。デフォルトのキーボード ショートカットの一覧については、キーボード アクションを処理するをご覧ください。
  • 高度なキーボード サポートをテストする(Tab キーと矢印キーによるキーボード ナビゲーション、Enter キーによるテキスト入力の確定、メディアアプリでの Space キーによる再生 / 一時停止など)
  • 基本的なマウス操作をテストします。たとえば、右クリックでコンテキスト メニューを表示する、カーソルを合わせるとアイコンが変化する、カスタム コンポーネントでマウスホイールまたはトラックパッドのスクロール イベントが発生する、などの操作をテストします。
  • アプリ固有の入力デバイスをテストする(タッチペン、ゲーム コントローラ、音楽アプリの MIDI コントローラなど)。
  • 高度な入力のサポートを検討する(パソコン環境でアプリの魅力を高めたい場合。DJ アプリ用のクロスフェーダーとしてのタッチパッド、ゲーム用のマウス キャプチャ、キーボードを多用するユーザー向けのキーボード ショートカットなど)

キーボード

アプリがキーボード入力にどのように応答するかは、大画面のユーザー エクスペリエンスに影響します。キーボード入力には、ナビゲーション、キーストローク、ショートカットの 3 種類があります。

タッチ操作中心のアプリでは、キーボード ナビゲーションが実装されることはほとんどありませんが、ユーザーは、アプリを使用しているときにキーボードに手を置いていると、キーボード ナビゲーションを期待します。キーボード ナビゲーションは、ユーザー補助機能を必要とするユーザーにとって、スマートフォン、タブレット、折りたたみ式デバイス、デスクトップ デバイスで不可欠な機能です。

多くのアプリでは、矢印キーと Tab キーによるナビゲーションは Android フレームワークによって自動的に処理されます。たとえば、Button はデフォルトでフォーカス可能であり、通常は追加のコードなしでキーボード ナビゲーションが機能します。デフォルトでフォーカス可能でないビューに対してキーボード ナビゲーションを有効にするには、そのビューをフォーカス可能としてマークします。これは、プログラムで行うことも、XML で行うこともできます。

Kotlin

yourView.isFocusable = true

Java

yourView.setFocusable(true);

代替策として、レイアウト ファイルで focusable 属性を設定する方法もあります。

android:focusable="true"

詳しくは、フォーカスの処理をご覧ください。

フォーカスが有効になっている場合、Android フレームワークは、フォーカス可能なすべてのビューに対して、その位置に基づいてナビゲーション マッピングを作成します。通常、これは想定どおりに機能し、それ以上の開発は必要ありません。デフォルトのマッピングがアプリのニーズに合わない場合は、次のようにマッピングをオーバーライドできます。

Kotlin

// Arrow keys
yourView.nextFocusLeftId = R.id.view_to_left
yourView.nextFocusRightId = R.id.view_to_right
yourView.nextFocusTopId = R.id.view_above
yourView.nextFocusBottomId = R.id.view_below
// Tab key
yourView.nextFocusForwardId = R.id.next_view

Java

// Arrow keys
yourView.setNextFocusLeftId(R.id.view_to_left);
yourView.setNextFocusRightId(R.id.view_to_left);
yourView.setNextFocusTopId(R.id.view_to_left);
yourView.setNextFocusBottomId(R.id.view_to_left);
// Tab key
yourView.setNextFocusForwardId(R.id.next_view);

キーボードのみを使用して、アプリのすべての UI 要素へのアクセスをテストします。よく使用する要素には、マウスやタップ入力なしでアクセスできるようにする必要があります。

ユーザー補助機能を必要とするユーザーにとってはキーボードのサポートが不可欠であることに留意してください。

キーストローク

画面上の仮想キーボード(IME)で処理されるテキスト入力(EditTextなど)の場合、アプリは追加の開発作業なしで、大画面デバイスで想定どおりに動作するはずです。フレームワークで予測できないキーストロークについては、アプリ側で動作を処理する必要があります。これは、特にカスタムビューを使用するアプリに当てはまります。

たとえば、Enter キーを使用してメッセージを送信するチャットアプリ、Space キーで再生を開始、停止するメディアアプリ、W、A、S、D キーで移動を制御するゲームなどがあります。

ほとんどのアプリは、onKeyUp() コールバックをオーバーライドし、受信したキーコードごとに想定される動作を追加します。

Kotlin

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) } }

Java

@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
    if (keyCode == KeyEvent.KEYCODE_ENTER) {
        sendMessage();
        return true;
    } else if (KeyEvent.KEYCODE_SPACE){
        playOrPauseMedia();
        return true;
    } else {
        return super.onKeyUp(keyCode, event);
    }
}

onKeyUp イベントは、キーが離されたときに発生します。このコールバックを使用すると、キーが長押しされた場合やゆっくり離された場合に、アプリが複数の onKeyDown イベントを処理する必要がなくなります。キーが押された瞬間や、ユーザーがキーを押し続けているかどうかを検出する必要があるゲームやアプリは、onKeyDown イベントをリッスンし、繰り返される onKeyDown イベントを独自に処理できます。

詳しくは、キーボード アクションの処理をご覧ください。

ショートカット

ハードウェア キーボードを使用する場合は、Ctrl、Alt、Shift、Meta キーを含む一般的なキーボード ショートカットが想定されます。アプリにショートカットが実装されていないと、ユーザーは不満を感じる可能性があります。上級ユーザーは、よく使用するアプリ固有のタスクのショートカットも好みます。ショートカットを使用すると、アプリが使いやすくなり、ショートカットのないアプリとの差別化を図ることができます。

一般的なショートカットには、Ctrl+S(保存)、Ctrl+Z(元に戻す)、Ctrl+Shift+Z(やり直し)などがあります。デフォルトのショートカットの一覧については、キーボード アクションの処理をご覧ください。

ショートカットを有効にするには、dispatchKeyShortcutEvent() を実装して、特定のキーコードのすべてのキーの組み合わせ(Alt、Ctrl、Shift、Meta)をインターセプトします。特定の修飾キーを確認するには、次のようにします。

Kotlin

override fun dispatchKeyShortcutEvent(event: KeyEvent): Boolean {
  return when (event.keyCode) {
    KeyEvent.KEYCODE_O -> {
      openFile() // Ctrl+O, Shift+O, Alt+O
      true
    }
    KeyEvent.KEYCODE_Z-> {
      if (event.isCtrlPressed) {
        if (event.isShiftPressed) {
          redoLastAction() // Ctrl+Shift+Z pressed
          true
        } else {
          undoLastAction() // Ctrl+Z pressed
          true
        }
      }
    }
    else -> {
      return super.dispatchKeyShortcutEvent(event)
    }
  }
}

Java

@Override
public boolean dispatchKeyShortcutEvent(KeyEvent event) {
  if (event.getKeyCode() == KeyEvent.KEYCODE_O) {
      openFile(); // Ctrl+O, Shift+O, Alt+O
      return true;
  } else if(event.getKeyCode() == KeyEvent.KEYCODE_Z) {
      if (event.isCtrlPressed()) {
          if (event.isShiftPressed()) {
              redoLastAction();
              return true;
          }
          else {
              undoLastAction();
              return true;
          }
      }
  }
  return super.dispatchKeyShortcutEvent(event);
}

ショートカット コードを他のキーストローク処理(onKeyUp() や onKeyDown() など)から分離すると、デフォルトで修飾キーが受け入れられるため、修飾キーのチェックを毎回手動で実装する必要がなくなります。すべての修飾キーの組み合わせを許可すると、異なるキーボード レイアウトやオペレーティング システムに慣れているユーザーにとっても便利です。

ただし、KeyEvent.isCtrlPressed()、KeyEvent.isShiftPressed()、KeyEvent.isAltPressed() をチェックすることで、onKeyUp() にショートカットを実装することもできます。変更されたキーの動作がショートカットではなくアプリの動作の変更である場合は、この方法の方が管理が容易になります。たとえば、ゲームで W が「前方に歩く」、Shift+W が「前方に走る」を意味する場合です。

Kotlin

override fun onKeyUp(keyCode: Int, event: KeyEvent): Boolean {
  return when(keyCode) {
    KeyEvent.KEYCODE_W-> {
      if (event.isShiftPressed) {
        if (event.isCtrlPressed) {
          flyForward() // Ctrl+Shift+W pressed
          true
        } else {
          runForward() // Shift+W pressed
          true
        }
      } else {
        walkForward() // W pressed
        true
      }
    }
    else -> super.onKeyUp(keyCode, event)
  }
}

Java

@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
    if (keyCode == KeyEvent.KEYCODE_W) {
        if (event.isShiftPressed()) {
            if (event.isCtrlPressed()) {
                flyForward(); // Ctrl+Shift+W pressed
                return true;
            } else {
                runForward(); // Shift+W pressed
                return true;
            }
        } else {
            walkForward();
            return true;
        }
    }
    return super.onKeyUp(keyCode, event);
}

キーボード ショートカットのヘルプもご覧ください。

タッチペン

多くの大画面デバイスにはタッチペンが付属しています。Android アプリは、タッチペンをタッチスクリーン入力として処理します。一部のデバイスには、Wacom Intuos などの USB または Bluetooth の描画テーブルが搭載されている場合があります。Android アプリは Bluetooth 入力を受信できますが、USB 入力は受信できません。

タッチペン イベントは、View#onTouchEvent() または View#onGenericMotionEvent() によってタッチスクリーン イベントとして報告され、SOURCE_STYLUS タイプの MotionEvent#getSource() が含まれます。

MotionEvent オブジェクトには、イベントに関する情報が含まれています。

履歴ポイント

Android は、入力イベントを一括でまとめてフレームごとに 1 回送信します。タッチペンは、ディスプレイよりもはるかに高い頻度でイベントを報告できます。描画アプリを作成する場合は、getHistorical API を使用して、最近発生したイベントを確認します。

パーム リジェクション

ユーザーがタッチペンを使用して描画、書き込み、またはアプリの操作を行うと、手のひらが画面に触れることがあります。タッチイベント(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 のメモ作成オプションを表示します。

  1. デベロッパー モードに切り替えて、デバイスを書き込み可能にする
  2. Ctrl+Alt+F2 キーを押してターミナルを開きます。
  3. sudo vi /etc/chrome_dev.conf コマンドを実行する
  4. i を押して編集し、ファイルの末尾の新しい行に --ash-enable-palette を追加します。
  5. Esc キーを押してから :、w、q を入力し、Enter キーを押して保存する
  6. Ctrl+Alt+F1 キーを押して、通常の ChromeOS UI に戻る
  7. ログアウトしてから再度ログインする

シェルフにタッチペン メニューが表示されます。

  • シェルフ内のタッチペン ボタンをタップし、[新しいメモ] を選択します。これにより、空白の描画メモが開きます。
  • スクリーンショットを撮ります。シェルフからタッチペン ボタン > [画面をキャプチャ] を選択するか、画像をダウンロードします。通知に [Annotate image] オプションが表示されます。これにより、画像にアノテーションを付けられる状態でアプリが起動します。

マウスとタッチパッドのサポート

ほとんどのアプリでは、一般的に、大画面中心のイベント(右クリック、ホバー、ドラッグ&ドロップ)のみを処理する必要があります。

右クリック

リストアイテムの長押しなど、アプリでコンテキスト メニューを表示するアクションは、右クリック イベントにも反応する必要があります。

右クリック イベントを処理するには、アプリで View.OnContextClickListener を登録する必要があります。

Kotlin

yourView.setOnContextClickListener {
    showContextMenu()
    true
}

Java

yourView.setOnContextClickListener(v -> {
    showContextMenu();
    return true;
});

コンテキスト メニューの作成について詳しくは、コンテキスト メニューを作成するをご覧ください。

カーソルを合わせる

ホバーイベントを処理することで、アプリのレイアウトを洗練された使いやすいものにできます。これは、カスタム ビュー:

Kotlin

// Change the icon to a "hand" pointer on hover.
// Highlight the view by changing the background.
yourView.setOnHoverListener { view, _ ->
    addVisualHighlighting(true)
    view.pointerIcon =
        PointerIcon.getSystemIcon(view.context, PointerIcon.TYPE_HAND)
    true // Listener consumes the event.
}

Java

// Change the icon to a "hand" pointer on hover.
// Highlight the view by changing the background.
yourView.setOnHoverListener((view, event) -> {
    addVisualHighlighting(true);
    view.setPointerIcon(
        PointerIcon.getSystemIcon(view.getContext(), PointerIcon.TYPE_HAND)
    );
    return true; // Listener consumes the event.
});

最も一般的な例は次の 2 つです。

  • マウスポインタのアイコンを変更して、要素がクリック可能または編集可能などのインタラクティブな動作を持つかどうかをユーザーに示す
  • 大規模なリストまたはグリッド内のアイテムにユーザーがカーソルを合わせたとき、そのアイテムに視覚的なフィードバックを追加する

ドラッグ&ドロップ

マルチウィンドウ環境では、ユーザーはアプリ間でアイテムをドラッグ&ドロップできることを期待します。これは、デスクトップ デバイスだけでなく、分割画面モードのタブレット、スマートフォン、折りたたみ式デバイスにも当てはまります。

ユーザーがアプリにアイテムをドラッグする可能性があるかどうかを考慮します。たとえば、写真エディタによる写真の受け取り、オーディオ プレーヤーによる音声ファイルの受け取り、描画プログラムによる写真の受け取りなどがあります。

ドラッグ&ドロップのサポートを追加するには、 ドラッグ&ドロップを有効にする を参照し、Android on ChromeOS — Implementing Drag & Drop のブログ投稿をご覧ください。

ChromeOS 向けの特別な考慮事項

  • アプリ外からドラッグしたアイテムにアクセスするには、requestDragAndDropPermissions() を使用して権限をリクエストします。
  • アイテムを他のアプリケーションにドラッグするには、View.DRAG_FLAG_GLOBAL フラグが必要です。

高度なポインタのサポート

マウス入力とタッチパッド入力を高度に処理するアプリでは、 View#onGenericMotionEvent()。SOURCE_MOUSE と SOURCE_TOUCHSCREEN を区別するには、[MotionEvent.getSource()][] を使用します。

MotionEvent オブジェクトを調べて、必要な動作を実装します。

  • 移動によって ACTION_HOVER_MOVE イベントを生成する。
  • ボタンによって ACTION_BUTTON_PRESS イベントと ACTION_BUTTON_RELEASE イベントを生成する。getButtonState() を使用して、すべてのマウスボタンとトラックパッド ボタンの現在の状態を確認することもできます。
  • マウスホイールのスクロールによって ACTION_SCROLL イベントを生成する。

ゲーム コントローラ

一部の大画面 Android デバイスは、最大 4 台のゲーム コントローラをサポートしています。標準の Android ゲーム コントローラ API を使用して、ゲーム コントローラを処理します(ゲーム コントローラをサポートするをご覧ください)。

ゲーム コントローラのボタンは、一般的なマッピングに従って一般的な値にマッピングされます。ただし、すべてのゲーム コントローラ メーカーが同じマッピング規則に従っているわけではありません。各種の一般的なコントローラ マッピングをユーザーが選択できるようにすると、エクスペリエンスを大幅に向上させることができます。詳しくは、ゲームパッドのボタン押下を処理するをご覧ください。

入力変換モード

ChromeOS では、デフォルトで入力変換モードが有効になっています。ほとんどの Android アプリの場合、このモードはパソコン環境で想定されるとおりに動作します。たとえば、タッチパッドでの 2 本指スクロール、マウスホイール スクロール、未加工のディスプレイ座標からウィンドウ座標へのマッピングを自動的に有効にすることなどです。通常、アプリ デベロッパーがこれらの動作を実装する必要はありません。

カスタムの入力動作をアプリに実装する(たとえば 2 本の指でタッチパッドをつまむカスタム操作を定義する)場合や、アプリが想定する入力イベントが入力変換によって提供されない場合は、Android マニフェストに次のタグを追加することにより、入力変換モードを無効にできます。

<uses-feature
    android:name="android.hardware.type.pc"
    android:required="false" />

参考情報