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

ユーザーが 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 つのキーイベントは type 属性を参照することで区別できます。KeyEvent

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

Space キーと Enter キーのクリック イベント

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

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

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

使用されていないキーイベント

使用されていないキーイベントは、イベントが発生したコンポーネントから、それを囲む外部コンポーネントに伝播されます。 次の例では、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")
    }
}

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

参考情報