OnApplyWindowInsetsListener in landscape mode to detect keyboard visibility changes

1.1k Views Asked by At

Thanks to this great answer here: https://stackoverflow.com/a/63595830/741795 we can now track when keyboard is shown in Android... Unfortunateñy I have found some issues with it.

It turns out that for API 29 and inferior, the OnApplyWindowInsetsListener.onApplyWindowInsets is only called:

  • when activity has the "adjustResize" flag in manifest
  • only works in portrait mode.

For api 30+ everything works in both portrait and landscape and without the flag.

Due to these limitations, for api 29- I am using a different method (see code below) using the OnGlobalLayoutListener.onGlobalLayout

// in activity onCreate():

val rootView = window.decorView.rootView
        if (isApi30()) {
            val isKeyBoardVisible = isKeyboardShownOlderApis(rootView)
            Log.d("KeyBoardVisibility", "initial keyboard state is: $isKeyBoardVisible for view: $rootView")
            ViewCompat.setOnApplyWindowInsetsListener(rootView, WindowInsetListener(isKeyBoardVisible))
        } else {
            rootView.viewTreeObserver.addOnGlobalLayoutListener(KeyBoardListener(rootView))
        }

then the actual listeners:

//TODO: beware of leaks use weak reference
    private class KeyBoardListener(val rootView: View) : ViewTreeObserver.OnGlobalLayoutListener {
        var isKeyBoardVisible: Boolean = false

        init {
            isKeyBoardVisible = isKeyboardShownOlderApis(rootView)
            Log.d("KeyBoardVisibility", "initial keyboard state is: $isKeyBoardVisible for view: $rootView")
        }

        /**
         * Does not get called in api 21-29 landscape when keyboard is opened/closed
         */
        override fun onGlobalLayout() {
            Log.d("KeyBoardVisibility", "onGlobalLayout called: orientation: " + rootView.resources.configuration.orientation)
            val keyBoardVisible = isKeyboardShownOlderApis(rootView)
            if (keyBoardVisible != isKeyBoardVisible) {
                Log.i("KeyBoardVisibility", "Keyboard is now ${if (keyBoardVisible) "visible" else "hidden"}")
                isKeyBoardVisible = keyBoardVisible
            }
        }
    }

    /**
     * Works api 30, 31
     * Works api 25 in portrait with android:windowSoftInputMode="adjustResize"
     */
    private class WindowInsetListener(initialKeyboardVisibility: Boolean) : OnApplyWindowInsetsListener {
        var isKeyBoardVisible = initialKeyboardVisibility

        @SuppressLint("LongLogTag")
        override fun onApplyWindowInsets(v: View?, insets: WindowInsetsCompat?): WindowInsetsCompat? {
            val keyBoardVisible = insets?.isVisible(ime()) ?: false
            Log.d("KeyBoardVisibility", "onApplyWindowInsets called: $v with keyboardVisibility: $keyBoardVisible")
            if (keyBoardVisible != isKeyBoardVisible) {
                Log.i("KeyBoardVisibilityChanged", "Keyboard is now ${if (keyBoardVisible) "visible" else "hidden"}")
                isKeyBoardVisible = keyBoardVisible
            }
            insets?.let {
                v?.onApplyWindowInsets(insets.toWindowInsets())
            }
            return insets
        }
    }
}

private fun isKeyboardShownOlderApis(rootView: View?): Boolean {
    if (rootView == null) {
        return false
    }
    val insets = ViewCompat.getRootWindowInsets(rootView)
    if (insets == null) {
        Log.i("KeyBoardVisibility", "insets is null")
    }
    return insets?.isVisible(ime()) ?: false
}

private fun isApi30() = Build.VERSION.SDK_INT >= 30

Problem: onGlobalLayout is not called when the keyboard is shown/hidden in landscape mode, I tried on many emulators and phones, it does work on a Tablet.

I have found this sample project: https://github.com/android/user-interface-samples/tree/master/WindowInsetsAnimation but in API 21-29 the methods also don't get called, which is fine for the animations but not in my case, so there must be a problem there. I searched everywhere and did not find any information about this at all.

More info: This will be an SDK code so I cannot expect users to set any flags in manifest or any fitsSystemWindows flags I am using androidx.core:core-ktx:1.5.0 but 1.6.0 has the same issue.

Any ideas of what I could try? I suspect the window is different since the keyboard occupies the whole screen together with the editText but why it works with new APIS?

0

There are 0 best solutions below