2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Compose TextFieldのキーボード対応(Compose Multiplatform含む)

Posted at

記事の内容としては、テキストフィールドを押したときに出てくるキーボードの振る舞いについてです。

最近個人的にCompose Multiplatformでアプリを試していて、両OSのキーボード対応を調べました。
発端はMultiplatformですが、あまり整理できていなかったので、この機会にまとめようと思います。

keyboardOptionsと、keyboardActionsを扱った後に、キーボードを出したときにテキストフィールドを隠さない設定などを書きたいと思います。

keyboardOptions

KeyboardOptionsの各パラメータについての説明です。

パラメータ 説明
capitalization 文字、単語、または文を自動的に大文字にするかどうかをキーボードに指示
autoCorrectEnabled 自動修正を有効にするかどうかをキーボードに指示。KeyboardType.EmailとKeyboardType.Uriで有効
keyboardType テキストフィールドで使用するキーボードタイプ。Unspecified, Text, Ascii, Number, Phone, Uri, Email, Password, NumberPassword, Decimalがある。数字だけ入力するキーボードなどを指定
imeAction IME アクション。Unspecified, Default, None, Go, Search, Send, Previous, Next, Doneがある。リターンキーの見た目が変わり、挙動も変わる。
platformImeOptions プラットフォーム固有の IME オプション
showKeyboardOnFocus デフォルトでtrueで、フォーカスしたときにキーボードを自動で表示する
hintLocales キーボードで言語切替をするときに、候補として表示させたいものを指定

日本語圏に限定するなら、このなかではkeyboardTypeとimeActionをよく使うのではないかと思います。

iOSのswift ui開発なら、keyboardTypeとsubmitLabelというmodifierが相当するようです。

こちらどのようなキーボードになるか、視覚的に整理しておきましょう。

(OSや機種、設定等によって画像とは異なる結果になることもある点に注意)

keyboardType Android iOS
KeyboardType.Unspecified
KeyboardType.Text
KeyboardType.Ascii
KeyboardType.Number
KeyboardType.Phone
KeyboardType.Uri
KeyboardType.Email
KeyboardType.Password
KeyboardType.NumberPassword
KeyboardType.Decimal
imeAction Android iOS
ImeAction.Unspecified
ImeAction.Default
ImeAction.None
ImeAction.Go
ImeAction.Search
ImeAction.Send
ImeAction.Previous
ImeAction.Next
ImeAction.Done

keyboardActions

imeActionで、returnのようなキーを押したときにトリガーされる操作を実装できます。
たとえば、imeAction.Sendしたときに、リクエストを送るようなことができます。

キーボードでビューが隠れる問題への対応

何もしなったり設定を誤ると、キーボードが現れたときに、意図せずビューが上に行ってしまったり、キーボードで後ろのビューが隠れてしまいます。

そのときはmanifestのactivityに、android:windowSoftInputMode="adjustResize"を追加します。これがないと、背景のビューが上に行ってしまい、上の部分が隠れてしまうようです。

しかしこの設定だけでは不十分です。この設定だけだとキーボードで後ろのビューが隠れてしまいます。imePaddingという修飾子が用意されているので、これを使えばテキストフィールドがキーボードに隠れないように、キーボードが現れた時だけ自動でpaddingを追加することができます。

@Composable
fun KeyboardPlayground(modifier: Modifier = Modifier) {
    Column(
        modifier = modifier
            .imePadding()
            .verticalScroll(rememberScrollState()),
    ) {
        AllKeyboardTypeTextField()
        AllImeActionTextField()
    }
}

注意すべきは、imePaddingとverticalScrollの順番。上記を逆にすると、キーボードが現れた瞬間テキストフィールドはキーボードに隠れてしまいます。

なぜかというと、余白がどこに追加されるか考えると理解できます。正しいimePadding.verticalScrollの順番だと、キーボードの余白は表示したいコンテンツの外に配置されます。逆だと、スクロール対象のコンテンツの一番下に配置されます。

正しい順序だとキーボードが出現するとコンテンツの表示領域は狭まり、コンテンツはオフセットされるような形になりテキストフィールドは見えます。

正しくない逆の順序だと、キーボードが出現してもコンテンツの表示領域は変わりません。スクロール量はキーボードが現れたところで変わらないので、テキストフィールドが隠れてしまいます。

ここは言葉だけで説明できる自身がないので図を書いておきます。
(赤枠で囲ったものがフォーカス対象のテキストフィールドです。)

スクリーンショット 2024-12-21 22.20.15.png

またimePaddingは外側にあるComposableに置いてもよいです。上記の場合だとKeyboardPlayground()の外側にScaffoldとかがあるなら、そちらに設定してもよいです。

Modifier.bringIntoViewRequester

Modifier.bringIntoViewRequesterを使って、ビューが表示領域に入るような実装を入れることができます。
なにかの条件が揃った時や、アクションがあったとき、自動でスクロールさせて特定のコンポーネントを表示させたいときに使います。
テキストフィールドフォーカスでテキストフィールドを表示したいような場合は前項のやり方で事足りるので、これを使わなくても良いと思います。
テキストフィールド以外でも対応できます。

以下は公式のサンプル

@Composable
fun BringIntoViewSample() {
    Row(Modifier.horizontalScroll(rememberScrollState())) {
        repeat(100) {
            val bringIntoViewRequester = remember { BringIntoViewRequester() }
            val coroutineScope = rememberCoroutineScope()
            Box(
                Modifier
                    // This associates the RelocationRequester with a Composable that wants to be
                    // brought into view.
                    .bringIntoViewRequester(bringIntoViewRequester)
                    .onFocusChanged {
                        if (it.isFocused) {
                            coroutineScope.launch {
                                // This sends a request to all parents that asks them to scroll so
                                // that this item is brought into view.
                                bringIntoViewRequester.bringIntoView()
                            }
                        }
                    }
                    .focusTarget()
            )
        }
    }
}

キーボード外の領域タップでキーボードを閉じたい時

親領域のComposableをフォーカス可能にして、FocusRequester.requestFocus()で、親にフォーカスを当てることで、テキストフィールドのフォーカスを外します。
indicationを指定しているのは、リップルエフェクトをなくすためです。

@Composable
fun KeyboardPlayground(modifier: Modifier = Modifier) {
    val focusRequester = remember { FocusRequester() }
    val interactionSource = remember { MutableInteractionSource() }
    Column(
        modifier = modifier
            .imePadding()
            .verticalScroll(rememberScrollState())
            .focusRequester(focusRequester)
            .focusable()
            .clickable(interactionSource = interactionSource, indication = null) {
                focusRequester.requestFocus()
            }
    ) {
        AllKeyboardTypeTextField()
        AllImeActionTextField()
    }
}

こちらの記事様(Jetpack Composeでも背景押したらTextFieldのフォーカスを外す - たくさんの自由帳)を参考にさせていただきました。

Compose multiplatformの注意点

これまで述べてきたものはCompose Multiplatformに対応していて、iOSでも同様の挙動が可能になります。

ただOSのキーボードの違いはあります。
iOSはnumberの場合にはreturnが表示されない、というものがありました。
こちらはOSのキーボードの仕様なので、注意しておきたいところです。

さいごに

いろいろな実装方法がありそうですが、自分のやり方をまとめてみました。
間違っているところがあればご指摘いただけると嬉しいです。

こちらにコードを公開しているのでよかったら参考までに。(ディレクトリ整理等でurl変更になってしまっていたら、こちら)

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?