IME表示時にTextFieldをスクロールしたい
ComposeのTextFieldはテキストを編集したときに、カーソルが表示領域に収まるように自動スクロールをしてくれます。
しかし画面いっぱいに広がっているようなTextFieldの場合、IMEの表示などでTextFieldの表示サイズが小さくなって、カーソルがIME裏に隠れたとしても、自動でカーソルを表示領域内に移動はしてくれません↓
| IME表示前 | IME表示後 |
|---|---|
![]() |
![]() |
そのためIMEに合わせてスクロールする && 既存のTextFieldのように編集時にもスクロールするTextFieldを作りたかった訳です。
一応AndroidViewでEditTextとScrollViewを組み合わせるか、RelocationRequesterを使用する方法などでも実装は可能かと思いますが、今回はComposeのTextFieldにあの手この手で、IME表示時とテキストの編集時にカーソルが表示領域内に自動スクロールするようなTextFieldを実装しました。
実装
Column(
modifier = modifier
.background(color = Color.White)
.systemBarsPadding()
.imePadding(),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Text(
text = "上のView",
modifier = Modifier.padding(bottom = 20.dp),
color = Color.Black,
)
val scrollState = rememberScrollState()
var textLayoutResult by remember { mutableStateOf<TextLayoutResult?>(null) }
var textFieldHeightPx by remember { mutableIntStateOf(0) }
var textFieldValue by remember { mutableStateOf(TextFieldValue()) }
LaunchedEffect(textFieldHeightPx, textFieldValue.selection.end) {
val layoutResult = textLayoutResult ?: return@LaunchedEffect
val cursorLine = layoutResult.getLineForOffset(textFieldValue.selection.end)
val cursorBottom = layoutResult.getLineBottom(cursorLine).roundToInt()
val cursorTop = layoutResult.getLineTop(cursorLine).roundToInt()
val scrollPosition = when {
cursorBottom > scrollState.value + textFieldHeightPx -> cursorBottom - textFieldHeightPx
cursorTop < scrollState.value -> cursorTop
else -> null
}
scrollPosition?.let { scrollState.scrollTo(it.coerceIn(0, scrollState.maxValue)) }
}
BasicTextField(
value = textFieldValue,
modifier = Modifier
.weight(1f)
.fillMaxSize()
.background(color = Color.Gray)
.onSizeChanged { textFieldHeightPx = it.height }
.verticalScroll(state = scrollState),
onValueChange = { textFieldValue = it },
onTextLayout = { textLayoutResult = it },
)
Text(
text = "下のView",
modifier = Modifier.padding(top = 20.dp),
color = Color.Black,
)
}
動作
↑の実装で、表示領域外の文章が編集されたときやIMEの表示に合わせてTextFieldが自動でスクロールします。
| 表示領域外の文章が編集された時 | IME表示後 |
|---|---|
![]() |
![]() |
解説
まずLaunchedEffectで、TextFieldの表示領域の高さ(textFieldHeightPx)が変更された時と、選択部分の末尾(textFieldValue.selection.end)が変更された時に自動スクロールを実行するようにします。
これでTextFieldが小さくなってカーソルが隠れたり、カーソルが隠れた状態で文字が入力されたりした時に自動でスクロールされます。
スクロールの処理は以下のような単純な引き算です↓
- カーソルの最上部、最下部の位置を
layoutResultからそれぞれ計算
-> 現在のスクロール位置がカーソルの最上部より下ならば、カーソルの最上部にスクロール
->現在のスクロール位置 + TextFieldの表示領域の高さがカーソルの最下部より上にならばカーソルの最下部 + TextFieldの表示領域の高さの位置(TextFieldの表示領域の一番下にカーソルの最下部が合う位置)にスクロール



