記事の内容としては、テキストフィールドを押したときに出てくるキーボードの振る舞いについてです。
最近個人的に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や機種、設定等によって画像とは異なる結果になることもある点に注意)
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の順番だと、キーボードの余白は表示したいコンテンツの外に配置されます。逆だと、スクロール対象のコンテンツの一番下に配置されます。
正しい順序だとキーボードが出現するとコンテンツの表示領域は狭まり、コンテンツはオフセットされるような形になりテキストフィールドは見えます。
正しくない逆の順序だと、キーボードが出現してもコンテンツの表示領域は変わりません。スクロール量はキーボードが現れたところで変わらないので、テキストフィールドが隠れてしまいます。
ここは言葉だけで説明できる自身がないので図を書いておきます。
(赤枠で囲ったものがフォーカス対象のテキストフィールドです。)
また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のキーボードの仕様なので、注意しておきたいところです。
さいごに
いろいろな実装方法がありそうですが、自分のやり方をまとめてみました。
間違っているところがあればご指摘いただけると嬉しいです。