TextField
でフォーカスした際にキーボードで入力欄が隠れてしまうことがあります。
横画面はもちろん、縦画面でもコンテンツ数が多い場合はよくある問題かと思います。
本記事ではBringIntoViewRequester
を用いた対応方法についてまとめました。
BringIntoViewRequesterとは?
For instance, you can call BringIntoViewRequester.bringIntoView
to make all the scrollable parents scroll so that the specified item is brought into parent bounds.
「親がスクロール可能な場合にbringIntoView
を呼び出すことにより、親をスクロールさせ指定されたアイテムが親の境界に入るように動作させることが出来ます」とのことです。
その為、含める際には対象のTextField
だけではなく親Compose
への注意も必要になります。
スクロール可能にしておく事や、キーボード表示領域への対応も必要になります。
導入の前に注意点
BringIntoViewRequester
はExperimentalFoundationApi
に指定されております(2022年3月現在)
This foundation API is experimental and is likely to change or be removed in the future.
引用元にも記載があるように実験的なものになっているため、将来的に変更または削除される可能性があります。
その為、プロダクトに導入する際は注意して下さい。
対応方法
TextFieldのBringIntoViewRequester対応
BringIntoViewRequester
を利用することで実現できます。
以前はRelocationRequesterを利用していましたが、こちらはdeprecated
になっています。
まず、スクロール先の指定になります。
スクロール対象のCompose
のModifier
に対して
bringIntoViewRequester
でRequester
を指定します。
これを行うことでRequester
に対して要求した際にこちらにスクロールするようになります。
次にスクロールの要求になります。
こちらはRequester
のbringIntoView
を呼び出すことによりスクロールが実行されます。
TextField
に対してフォーカスした際に発火させるにはonFocusEvent
を利用します。
onFocusEvent
でState
がFocused
の際にのみbringIntoView
を実行させています。
途中に含まれているdelay
に関しましては本来必要無いのですが、
キーボードのアニメーションと被る場合に動作しなくなる為含めています。
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun BringIntoTextFiled() {
val requester = remember { BringIntoViewRequester() }
val coroutineScope = rememberCoroutineScope()
var text by remember { mutableStateOf("hogehoge") }
Column(
modifier = Modifier
.fillMaxSize()
.verticalScroll(rememberScrollState())
.padding(vertical = 1000.dp)
) {
TextField(
value = text,
onValueChange = { text = it },
modifier = Modifier
.bringIntoViewRequester(requester)
.onFocusEvent { focusState ->
if (focusState.isFocused) {
coroutineScope.launch {
delay(200)
requester.bringIntoView()
}
}
}
)
}
}
キーボード表示領域に対応する
上記の該当箇所の対応のみでは上手く自動スクロールが出来ません。
理由としましてはキーボードが表示された際にActivity
の表示領域とキーボードの表示領域が重なっており、
対象のTextField
が領域外になっていないという判定の為です。
対応としてadjustResize
をActivity
に対して設定すると、
キーボードが表示された際にActivityの表示領域がキーボードの表示領域に合わせて変更される為、
上手く動作するようになります。
<activity
android:name=".SampleActivity"
android:windowSoftInputMode="adjustResize"/>
これでTextField
へのフォーカス時に自動スクロールされるようになりました。
LazyColumnを用いた場合の挙動について
LazyColumnを用いた場合ですが上手く動作させることが出来ませんでした。
フォーカスし、キーボードが表示された直後すぐにキーボードが非表示になってしまいました。
こちらについては後日調査&対応しようと思います。
入力欄が複数ある場合について
BringIntoViewRequester
はTextField
に対して個別に作成する必要があります。
以下のFailed Case
のように共通で利用した場合は想定した動作になりません。
✗ Failed Case
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun SampleField() {
val requester = remember { BringIntoViewRequester() }
val coroutineScope = rememberCoroutineScope()
var text by remember { mutableStateOf("hogehoge") }
TextField(
value = text,
onValueChange = {
text = it
},
Column {
TextField(
value = "hogehage",
onValueChange = {},
modifier = Modifier
.bringIntoViewRequester(requester)
.onFocusEvent { focusState ->
if (focusState.isFocused) {
coroutineScope.launch {
delay(200)
requester.bringIntoView()
}
}
}
)
TextField(
value = "hogehage",
onValueChange = {},
modifier = Modifier
.bringIntoViewRequester(requester)
.onFocusEvent { focusState ->
if (focusState.isFocused) {
coroutineScope.launch {
delay(200)
requester.bringIntoView()
}
}
}
)
}
}
○ Success Case
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun SampleField() {
val requester1 = remember { BringIntoViewRequester() }
val requester2 = remember { BringIntoViewRequester() }
val coroutineScope = rememberCoroutineScope()
Column {
TextField(
value = "hogehage",
onValueChange = {},
modifier = Modifier
.bringIntoViewRequester(requester1)
.onFocusEvent { focusState ->
if (focusState.isFocused) {
coroutineScope.launch {
delay(200)
requester1.bringIntoView()
}
}
},
)
TextField(
value = "hogehage",
onValueChange = {},
modifier = Modifier
.bringIntoViewRequester(requester2)
.onFocusEvent { focusState ->
if (focusState.isFocused) {
coroutineScope.launch {
delay(200)
requester2.bringIntoView()
}
}
},
)
}
}
上記のSuccess Case
も冗長なので1つのComponent
としてまとめ使い回せるようにしましょう。
以上