はじめに
TextFieldにフォーカスが当たるとキーボードが表示されます。このキーボートはOSによって自動的に表示されるものですが、表示開始直前を検知したいということがあったので書き残しておきます
いつ
- キーボード表示設定を
windowSoftInputMode:adjustResize
にしている - 画面下部にフローティングボタンやタブといった(アプリの要件によっては)入力時に不要なUIがあり、コンテンツ領域を圧迫してしまっているとき
これらのときに、「キーボード表示時に下部のタブ等のUIも一緒にせり上がってしまう」という問題がありそれを解決するために必要となりました。
やりたいこと
キーボードが表示されているときには対象のUIを非表示にしたい
- ただし表示されてから非表示にすると遅いので、表示処理が開始される
前
に消したい - キーボードが表示中かどうかの状態を取れるようにしてScreen側で柔軟に使いたい
実装
WindowInsetsAnimationを使う。
override fun onPrepare(animation: WindowInsetsAnimation) {
val type = animation.typeMask
if ((type and WindowInsetsCompat.Type.ime()) != 0) {
// 準備時の表示状態(表示前 or 閉じる前)
decorView.rootWindowInsets.isVisible(WindowInsetsCompat.Type.ime())
}
}
onPrepareはWindowInsetsAnimationが開始される前に処理が入るため、ここでキーボードが今現在どのような状態なのかを判定することができる。
全体コード
上記を使って良い感じにキーボードの状態を取れるようにしたもの。
enum class Keyboard {
Opened, Closed;
val isVisible: Boolean
get() = this == Opened
}
@Composable
fun rememberKeyboardState(): Keyboard {
val context = LocalContext.current
val activity = context.findActivity() // ※1
val decorView = activity.window.decorView
var isVisible by remember {
mutableStateOf(false)
}
DisposableEffect(decorView) {
val callback = object : WindowInsetsAnimation.Callback(DISPATCH_MODE_STOP) {
override fun onPrepare(animation: WindowInsetsAnimation) {
val type = animation.typeMask
if ((type and WindowInsetsCompat.Type.ime()) != 0) {
isVisible = !decorView.rootWindowInsets.isVisible(WindowInsetsCompat.Type.ime())
}
}
override fun onProgress(
insets: WindowInsets,
animations: MutableList<WindowInsetsAnimation>
): WindowInsets {
return insets
}
}
decorView.setWindowInsetsAnimationCallback(callback)
onDispose {
decorView.setWindowInsetsAnimationCallback(null)
}
}
return remember(isVisible) {
if (isVisible) {
Keyboard.Opened
} else {
Keyboard.Closed
}
}
}
onPrepareで取れた状態を反転させてStateを更新することで、表示が切り替わる直前の状態を保持できるようにしています。
@Composable
fun SampleScreen() {
val keyboardState = rememberKeyboardState()
if (!keyboardState.isVisible) {
BottomContent()
}
}
※1 findActivity()はこの記事を参考にしたものです。(https://stackoverflow.com/questions/64675386/how-to-get-activity-in-compose)
ComposableからActivityを取る正規の方法が分からなかったため
※ Build.VERSION_CODES.R以上が必要だったりしますが、記事では省略しています。
終わりに
なんかたまにボタンがチラついて見える場合があって気になる...
試行錯誤して作ったんですが、もっと一般的な方法があるのかもしれない