Reliably showing the Android keyboard for ViewGroups too
The other day, I was browsing through the Android blog world (make sure to check out https://blog.danlew.net/), and found this really awesome blog post from the developers over at Square about reliably displaying the Android keyboard, which is controlled by a rather difficult API.
After trying out their code, however, I realized that it did not work for my purpose which was to focus a SearchView
after clicking a button. The bug was really strange, because while we had called requestFocus()
on the SearchView
, when we queried it for isFocused
, it would return false
! Turns out, after a lot of struggling, I found that requestFocus()
actually may give focus to one of the descendants of the View
instead. SearchView
is actually a ViewGroup
, so focus was actually given to a TextView
that was a child of the SearchView
.
After learning about this, the fix was easy: Instead of only directly checking the SearchView
for isFocused
, do a recursive check on the focusedChild
of the SearchView
. focusedChild
will return the child View
that either has focus or contains a descendant View
with focus, or null
if no descendants have focus. The code worked something like this:
fun View.getFocusedDescendant() =
when {
isFocused -> this
this is ViewGroup -> focusedChild.getFocusedDescendant()
else -> null
}
Including this fix, the new code for reliably showing the Android Keyboard becomes this:
fun View.focusAndShowKeyboard() {
/**
* This is to be called when the window already has focus.
*/
fun View.showTheKeyboardNow() {
/**
* This finds the descedant of the View that has focus.
*/
fun View.getFocusedDescendant() =
when {
isFocused -> this
this is ViewGroup -> focusedChild.getFocusedDescendant()
else -> null
}
val focusedDescendant = getFocusedDescendant()
if (focusedDescendant != null) {
post {
// We still post the call, just in case we are being notified of the windows focus
// but InputMethodManager didn't get properly setup yet.
val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.showSoftInput(focusedDescendant, InputMethodManager.SHOW_IMPLICIT)
}
}
}
requestFocus()
if (hasWindowFocus()) {
// No need to wait for the window to get focus.
showTheKeyboardNow()
} else {
// We need to wait until the window gets focus.
viewTreeObserver.addOnWindowFocusChangeListener(
object : ViewTreeObserver.OnWindowFocusChangeListener {
override fun onWindowFocusChanged(hasFocus: Boolean) {
// This notification will arrive just before the InputMethodManager gets set up.
if (hasFocus) {
this@focusAndShowKeyboard.showTheKeyboardNow()
// It’s very important to remove this listener once we are done.
viewTreeObserver.removeOnWindowFocusChangeListener(this)
}
}
})
}
}
After this fix, the keyboard showed up finally as expected!
Another unrelated, but exciting note about newer versions of Android, is that there is finally a way to check if the soft keyboard is showing without needing to do screen measurements! As of Android R, you can use the WindowInsets API to check like so:
One of my favourite changes in #AndroidR is the revamp of the WindowInsets API
— Chris Banes (@chrisbanes) February 20, 2020
For example, we now have way to detect when _and_ where the IME is visible on screen ⌨️📐 pic.twitter.com/Wy95yhGzFK
Comments
Post a Comment