0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

[Android]ソフトキーボードが表示開始される直前を検知する

Last updated at Posted at 2025-05-25

はじめに

TextFieldにフォーカスが当たるとキーボードが表示されます。このキーボートはOSによって自動的に表示されるものですが、表示開始直前を検知したいということがあったので書き残しておきます

いつ

  • キーボード表示設定をwindowSoftInputMode:adjustResizeにしている
  • 画面下部にフローティングボタンやタブといった(アプリの要件によっては)入力時に不要なUIがあり、コンテンツ領域を圧迫してしまっているとき

これらのときに、「キーボード表示時に下部のタブ等のUIも一緒にせり上がってしまう」という問題がありそれを解決するために必要となりました。

8843973fd2a0aa50ecc3eccbc02e463a.gif

やりたいこと

キーボードが表示されているときには対象の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()
    }
}

82378ff963e6da6ca3b855e82a7fb866.gif

※1 findActivity()はこの記事を参考にしたものです。(https://stackoverflow.com/questions/64675386/how-to-get-activity-in-compose)
ComposableからActivityを取る正規の方法が分からなかったため

※ Build.VERSION_CODES.R以上が必要だったりしますが、記事では省略しています。

終わりに

なんかたまにボタンがチラついて見える場合があって気になる...
試行錯誤して作ったんですが、もっと一般的な方法があるのかもしれない

0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?