キーボード アクションを処理する

ユーザーが TextField などの編集可能なテキスト コンポーネントにフォーカスを合わせ、デバイスにハードウェア キーボードが接続されている場合、すべての入力はシステムによって処理されます。キーイベントを処理することで、キーボード ショートカットを提供できます。

デフォルトのキーボード ショートカット

次のキーボード ショートカットは、すぐに使用できます。

キーボード ショートカット 操作 ショートカットをサポートするコンポーザブル
Shift+Ctrl+左矢印/右矢印 単語の先頭/末尾までのテキストを選択する BasicTextField、TextField
Shift+Ctrl+↑/↓ 段落の先頭/末尾までのテキストを選択する BasicTextField、TextField
Shift+Alt+上矢印/下矢印、または Shift+Meta+左矢印/右矢印 テキストの先頭/末尾までのテキストを選択する BasicTextField、TextField
Shift+左矢印/右矢印 キャラクターを選択 BasicTextField、TextField
Ctrl+A すべて選択 BasicTextField、TextField
Ctrl+C/Ctrl+X/Ctrl+V コピー/切り取り/貼り付け BasicTextField、TextField
Ctrl+Z/Ctrl+Shift+Z 元に戻す / やり直す BasicTextField、TextField
PageDown/PageUp スクロール LazyColumn、verticalScroll 修飾子、scrollable 修飾子

重要なイベント

Compose では、onKeyEvent 修飾子を使用して個々のキーストロークを処理できます。この修飾子は、変更されたコンポーネントがキーイベントを受け取ったときに呼び出されるラムダを受け取ります。キーイベントは KeyEvent オブジェクトとして記述されます。各キーイベントの情報は、onKeyEvent 修飾子に渡されたラムダ内のオブジェクトを参照することで取得できます。

キーストロークは 2 つのキーイベントを送信します。1 つはユーザーがキーを押したときにトリガーされ、もう 1 つはキーが離されたときにトリガーされます。2 つのキーイベントを区別するには、KeyEvent オブジェクトの type 属性を参照します。

onKeyEvent ラムダの戻り値は、キーイベントが処理されたかどうかを示します。アプリがキーイベントを処理する場合は true を返します。これにより、イベントの伝播が停止します。

次のスニペットは、ユーザーが Box コンポーネントで S キーを離したときに doSomething() 関数を呼び出す方法を示しています。

Box(
    modifier = Modifier
        .onKeyEvent {
            if (
                it.type == KeyEventType.KeyUp &&
                it.key == Key.S
            ) {
                doSomething()
                true
            } else {
                false
            }
        }
        .focusable()
) {
    Text("Press S key")
}

修飾キー

KeyEvent オブジェクトには、修飾キーが押されているかどうかを示す次の属性があります。

アプリが処理するキーイベントを具体的に記述します。 次のスニペットは、ユーザーが S キーのみを離した場合にのみ doSomething() 関数を呼び出します。ユーザーが Shift キーなどの修飾キーを押した場合、アプリは関数を呼び出しません。

Box(
    modifier = Modifier
        .focusable()
        .onKeyEvent {
            if (
                it.type == KeyEventType.KeyUp &&
                it.key == Key.S &&
                !it.isAltPressed &&
                !it.isCtrlPressed &&
                !it.isMetaPressed &&
                !it.isShiftPressed
            ) {
                doSomething()
                true
            } else {
                false
            }
        }
) {
    Text("Press S key with a modifier key")
}

スペースバーと Enter キーのクリック イベント

Space キーと Enter キーでもクリック イベントがトリガーされます。たとえば、次のようにクリック イベントを処理することで、ユーザーは Space キーまたは Enter キーを使用してメディア再生を切り替える(再生または一時停止する)ことができます。

MediaPlayer(modifier = Modifier.clickable { togglePausePlay() })

clickable 修飾子はキーイベントをインターセプトし、Spacebar キーまたは Enter キーが押されたときに onClick() コールバックを呼び出します。そのため、スニペットで Spacebar キーまたは Enter キーを押すと、togglePausePlay() 関数が呼び出されます。

未消費のキーイベント

使用されなかったキーイベントは、イベントが発生したコンポーネントから、それを囲む外側のコンポーネントに伝播されます。以下の例では、S キーが離されたときに InnerComponent がキーイベントを消費するため、OuterComponent は S キーを離すことでトリガーされるキーイベントを受け取りません。そのため、actionB() 関数は呼び出されません。

InnerComponent の他のキーイベント(D キーを離すなど)は、OuterComponent で処理できます。actionC() 関数が呼び出されるのは、D キーを離すキーイベントが OuterComponent に伝播されるためです。

OuterComponent(
    modifier = Modifier.onKeyEvent {
        when {
            it.type == KeyEventType.KeyUp && it.key == Key.S -> {
                actionB() // This function is never called.
                true
            }

            it.type == KeyEventType.KeyUp && it.key == Key.D -> {
                actionC()
                true
            }

            else -> false
        }
    }
) {
    InnerComponent(
        modifier = Modifier.onKeyEvent {
            if (it.type == KeyEventType.KeyUp && it.key == Key.S) {
                actionA()
                true
            } else {
                false
            }
        }
    )
}

onKeyPreviewEvent 修飾子

ユースケースによっては、デフォルトのアクションがトリガーされる前にキーイベントをインターセプトすることが必要になります。TextField にカスタム ショートカットを追加するのが一般的です。次のスニペットでは、ユーザーが Tab キーを押して、フォーカス可能な次のコンポーネントに移動できるようにしています。

val focusManager = LocalFocusManager.current
var textFieldValue by remember { mutableStateOf(TextFieldValue()) }

TextField(
    textFieldValue,
    onValueChange = {
        textFieldValue = it
    },
    modifier = Modifier.onPreviewKeyEvent {
        if (it.type == KeyEventType.KeyUp && it.key == Key.Tab) {
            focusManager.moveFocus(FocusDirection.Next)
            true
        } else {
            false
        }
    }
)

デフォルトでは、TextField コンポーネントは、ユーザーが Tab キーを押すたびにタブ文字を追加します。これは、キーイベントが onKeyEvent 修飾子で処理される場合でも同様です。タブ文字を追加せずにキーボード フォーカスを移動するには、スニペットのように、キーイベントに関連付けられたアクションをトリガーする前にキーイベントを処理します。onKeyPreviewEvent() ラムダは、true を返すことでキーイベントをインターセプトします。

親コンポーネントは、子で発生するキーイベントをインターセプトできます。次のスニペットでは、ユーザーが S キーを押すと、actionForPreview() 関数ではなく previewSKey() 関数が呼び出されます。

Column(
    modifier = Modifier.onPreviewKeyEvent {
        if (it.key == Key.S) {
            previewSKey()
            true
        } else {
            false
        }
    }
) {
    Box(
        modifier = Modifier
            .focusable()
            .onPreviewKeyEvent {
                actionForPreview(it)
                false
            }
            .onKeyEvent {
                actionForKeyEvent(it)
                true
            }
    ) {
        Text("Press any key")
    }
}

ユーザーが Tab キーを押しても、Box コンポーネントの onPreviewKeyEvent() ラムダはトリガーされません。onPreviewKeyEvent() ラムダは、最初に親コンポーネントで呼び出され、次に子コンポーネントの onPreviewKeyEvent() が呼び出されます。この動作を利用して、画面全体のキーボード ショートカットを実装できます。

参考情報