入力イベントの概要

Compose を試す
Jetpack Compose は Android で推奨される UI ツールキットです。Compose でタップと入力を使用する方法について説明します。

Android には、ユーザーによるアプリの操作からイベントをインターセプトする方法が複数あります。 ユーザー インターフェース内のイベントをインターセプトする場合は、ユーザーが操作する特定の View オブジェクトからイベントをキャプチャするアプローチが考えられます。View クラスは、そのための手段を提供します。

レイアウトを構成するために使用する各種の View クラスには、UI イベントの処理に役立つパブリック コールバック メソッドがいくつか含まれています。これらのメソッドは、対応するアクションがオブジェクトで発生したときに、Android フレームワークによって呼び出されます。たとえば、ビュー(ボタンなど)がタップされると、そのオブジェクトで onTouchEvent() メソッドが呼び出されます。これをインターセプトするには、クラスを拡張してメソッドをオーバーライドする必要があります。ただし、このようなイベントを処理するために、その都度 View オブジェクトを拡張するのは実際的ではありません。そのために、View クラスには、もっと簡単に定義できるコールバックを持つネストされたインターフェースのコレクションも含まれています。これらのインターフェースはイベント リスナーと呼ばれ、ユーザーによる UI の操作をキャプチャする手段として使用されます。

一般的にはイベント リスナーを使用してユーザー操作をリッスンしますが、カスタム コンポーネントを作成するために View クラスを拡張する場合もあります。たとえば、Button クラスを拡張して、凝った機能を提供することもできます。その場合は、クラスのイベント ハンドラを使用して、クラスのデフォルトのイベント動作を定義できます。

イベント リスナー

イベント リスナーは、単一のコールバック メソッドを含む、View クラスのインターフェースです。以下のメソッドは、イベント リスナーが登録されているビューがユーザーによる UI アイテムの操作によってトリガーされたときに、Android フレームワークによって呼び出されます。

イベント リスナー インターフェースには、以下のコールバック メソッドが含まれています。

onClick()
View.OnClickListener のメソッド。 ユーザーがアイテムをタップしたとき(タッチモードの場合)、またはナビゲーション キーかトラックボールでアイテムにフォーカスを合わせて適切な「Enter」キーかトラックボールを押したときに、呼び出されます。
onLongClick()
View.OnLongClickListener のメソッド。 ユーザーがアイテムを長押ししたとき(タッチモードの場合)、またはナビゲーション キーかトラックボールでアイテムにフォーカスを合わせて適切な「Enter」キーかトラックボールを長押し(1 秒間)したときに呼び出されます。
onFocusChange()
View.OnFocusChangeListener のメソッド。 ユーザーがナビゲーション キーかトラックボールを使用して、アイテムに移動したときまたはアイテムから移動したときに、呼び出されます。
onKey()
View.OnKeyListener のメソッド。 ユーザーがアイテムにフォーカスを合わせてデバイスのハードウェア キーを押したときまたは離したときに、呼び出されます。
onTouch()
View.OnTouchListener のメソッド。 画面(アイテムの境界内)で、タッチイベントとして判断されるアクション(押す、離す、移動するなどの操作)をユーザーが行ったときに呼び出されます。
onCreateContextMenu()
View.OnCreateContextMenuListener のメソッド。コンテキスト メニューが(「長押しクリック」を続けた結果として)ビルドされたときに呼び出されます。コンテキスト メニューの詳細については、メニューのデベロッパー ガイドをご覧ください。

これらのメソッドは、それぞれのインターフェースに含まれる唯一のメソッドです。これらのメソッドのいずれかを定義してイベントを処理するには、ネストされたインターフェースをアクティビティに実装するか、匿名クラスとして定義します。 次に、実装のインスタンスをそれぞれの View.set...Listener() メソッドに渡します(たとえば、setOnClickListener() を呼び出して、OnClickListener の実装を渡します)。

次の例は、ボタンの on-click リスナーを登録する方法を示しています。

Kotlin

protected void onCreate(savedValues: Bundle) {
    ...
    val button: Button = findViewById(R.id.corky)
    // Register the onClick listener with the implementation above
    button.setOnClickListener { view ->
        // do something when the button is clicked
    }
    ...
}

Java

// Create an anonymous implementation of OnClickListener
private OnClickListener corkyListener = new OnClickListener() {
    public void onClick(View v) {
      // do something when the button is clicked
    }
};

protected void onCreate(Bundle savedValues) {
    ...
    // Capture our button from layout
    Button button = (Button)findViewById(R.id.corky);
    // Register the onClick listener with the implementation above
    button.setOnClickListener(corkyListener);
    ...
}

OnClickListener をアクティビティの一部として実装する方法も有用です。 この方法では、追加のクラスの読み込みとオブジェクトの割り当てを回避できます。次に例を示します。

Kotlin

class ExampleActivity : Activity(), OnClickListener {
  
    protected fun onCreate(savedValues: Bundle) {
        val button: Button = findViewById(R.id.corky)
        button.setOnClickListener(this)
    }

    // Implement the OnClickListener callback
    fun onClick(v: View) {
        // do something when the button is clicked
    }
}

Java

public class ExampleActivity extends Activity implements OnClickListener {
    protected void onCreate(Bundle savedValues) {
        ...
        Button button = (Button)findViewById(R.id.corky);
        button.setOnClickListener(this);
    }

    // Implement the OnClickListener callback
    public void onClick(View v) {
      // do something when the button is clicked
    }
    ...
}

上記の例の onClick() コールバックには戻り値がありませんが、イベント リスナー メソッドによってはブール値を返す必要があることにご注意ください。ブール値を返す理由は、イベントによって異なります。いくつかのメソッドについて、その理由を示します。

  • onLongClick() - イベントの処理を終えてこれ以上処理を続けるべきでないかどうかを示すブール値を返します。 つまり、イベントの処理を終えてここで停止する必要があることを示す場合は、true を返します。イベントを処理していない場合および / または他の on-click リスナーにイベントを引き継ぐ必要がある場合は、false を返します。
  • onKey() - イベントの処理を終えてこれ以上処理を続けるべきでないかどうかを示すブール値を返します。 つまり、イベントの処理を終えてここで停止する必要があることを示す場合は、true を返します。イベントを処理していない場合および / または他の on-key リスナーにイベントを引き継ぐ必要がある場合は、false を返します。
  • onTouch() - リスナーがこのイベントを処理するかどうかを示すブール値を返します。ここで重要なのは、このイベントが相互にフォローし合う複数のアクションを含む可能性があることです。DOWN アクション イベントを受け取ったときに false を返すのは、イベントを処理しておらず、このイベントの後に発生するアクションにも関心がない場合です。したがって、指による操作や最終的な UP アクション イベントなど、イベント内の他のアクションのためにメソッドが呼び出されることはありません。

ハードウェア キーイベントは、常に、現在フォーカスされているビューに送信されます。ビュー階層の一番上から下に向かって、適切な宛先に到達するまでディスパッチされます。ビュー(またはビューの子)が現在フォーカスされている場合は、dispatchKeyEvent() メソッドを通してイベントが移動していることを確認できます。ビューを介してキーイベントをキャプチャする別の方法として、onKeyDown()onKeyUp() を使用してアクティビティ内のすべてのイベントを受け取ることもできます。

また、アプリのテキスト入力について検討する際は、多くのデバイスにはソフトウェア入力メソッドしかないことにご注意ください。ソフトウェア入力メソッドではキーの使用は必須ではなく、音声入力や手書き入力を利用できるものもあります。入力メソッドがキーボードに似たインターフェースを提供する場合でも、それによって onKeyDown() ファミリーのイベントがトリガーされることは通常ありません。アプリの使用をハードウェア キーボード付きのデバイスに限定する場合を除き、特定のキーを押して制御する必要がある UI は作成しないでください。特に、ユーザーがリターンキーを押したときに入力を検証する方法には頼らず、その代わりに IME_ACTION_DONE などのアクションを使用してアプリが期待する反応を入力メソッドに伝え、それによって入力メソッドが UI に意味のある変更を加えられるようにしてください。ソフトウェア入力メソッドの動作を推測し、その推測を信じて書式設定済みのテキストをアプリに提供することは避けてください。

注: Android は、最初にイベント ハンドラを呼び出し、次にクラス定義から適切なデフォルト ハンドラを呼び出します。そのため、これらのイベント リスナーから true が返されると、イベントは他のイベント リスナーに伝播されなくなり、ビュー内のデフォルトのイベント ハンドラへのコールバックもブロックされます。したがって、true を返す場合は、本当にイベントを終了してよいかを確認してください。

イベント ハンドラ

ビューからカスタム コンポーネントを作成する場合は、デフォルトのイベント ハンドラとして使用するいくつかのコールバック メソッドを定義できます。カスタムビュー コンポーネントに関するドキュメントでは、イベント処理に使用される次のような一般的なコールバックについて確認できます。

これ以外に、View クラスに含まれていないがイベントの処理方法に直接影響するメソッドがいくつかあり、それらについても考慮する必要があります。レイアウト内の複雑なイベントを管理する場合は、以下のメソッドの使用を検討してください。

タッチモード

ユーザーが方向キーまたはトラックボールを使用してユーザー インターフェースを操作する場合、入力を受け入れるアイテムがどれかをユーザーが把握できるように、操作可能なアイテム(ボタンなど)にフォーカスを与える必要があります。ただし、デバイスがタッチ機能を備えており、ユーザーがインターフェースにタッチして操作を開始した場合、アイテムをハイライト表示したり特定のビューにフォーカスを与えたりする必要はなくなります。「タッチモード」という操作モードが存在するのは、このためです。

タッチ対応デバイスでは、ユーザーが画面にタッチすると、デバイスはタッチモードになります。それ以降は、isFocusableInTouchMode() が true のビュー(テキスト編集ウィジェットなど)だけがフォーカス可能になります。 タッチ可能な他のビュー(ボタンなど)は、タッチされてもフォーカスを取得しません。押されたときに on-click リスナーを起動するだけです。

ユーザーが方向キーを押すか、トラックボールでスクロールすると、デバイスはタッチモードから抜け、ビューがフォーカスを取得します。これにより、ユーザーは画面にタッチせずにユーザー インターフェースの操作を再開できます。

タッチモードの状態は、システム全体(すべてのウィンドウとアクティビティ)で維持されます。 現在の状態をクエリするには、isInTouchMode() を呼び出して、デバイスが現在タッチモードかどうかを確認します。

フォーカスの処理

フレームワークは、ユーザー入力に応じて、所定のフォーカス移動を処理します。 そのようなフォーカス移動には、ビューが削除されたり非表示になったり、新しいビューが利用可能になったりしたことによるフォーカスの変更が含まれます。ビューは、isFocusable() メソッドを通して、フォーカスを取得しようとしていることを示します。ビューがフォーカスを取得できるかどうかを変更するには、setFocusable() を呼び出します。タッチモード中は、isFocusableInTouchMode() を使用して、ビューがフォーカスを取得できるかどうかをクエリできます。 ビューがフォーカスを取得できるかどうかは、setFocusableInTouchMode() で変更できます。

Android 9(API レベル 28)以降を搭載したデバイスでは、アクティビティは初期フォーカスを割り当てません。割り当てたい場合は、明示的に初期フォーカスをリクエストする必要があります。

フォーカス移動は、指定された方向の最も近くにあるアイテムを見つけるアルゴリズムに基づいて行われます。しかし、デフォルトのアルゴリズムがデベロッパーの意図する動作と一致しないことがまれにあります。そのような場合は、レイアウト ファイル内の XML 属性 nextFocusDownnextFocusLeftnextFocusRightnextFocusUp を明示的にオーバーライドできます。これらの属性のいずれかを、フォーカスの「移動元」のビューに追加します。フォーカスの「移動先」のビューの ID を属性の値として定義します。次に例を示します。

<LinearLayout
    android:orientation="vertical"
    ... >
  <Button android:id="@+id/top"
          android:nextFocusUp="@+id/bottom"
          ... />
  <Button android:id="@+id/bottom"
          android:nextFocusDown="@+id/top"
          ... />
</LinearLayout>

本来、この垂直方向のレイアウトでは、1 番目のボタンから上に移動しようとしても、2 番目のボタンから下に移動しようしても、フォーカスはどこにも移動しません。しかし、上記の例では、一番上のボタンによって一番下のボタンが nextFocusUp として定義されています(逆もまた同様)。これにより、ナビゲーション フォーカスは一番上から一番下、一番下から一番上に循環します。

UI 内でビューをフォーカス可能として宣言するには(元々フォーカス可能でなかった場合)、レイアウト宣言で android:focusable XML 属性をビューに追加します。 値を true に設定します。また、android:focusableInTouchMode を使用して、タッチモード中にビューをフォーカス可能として宣言することもできます。

特定のビューがフォーカスを取得することをリクエストするには、requestFocus() を呼び出します。

フォーカス イベントをリッスンする(それによってビューがフォーカスを取得または喪失したときに通知を受け取る)には、イベント リスナー セクションの説明に従って、onFocusChange() を使用します。