I have an activity with two EditText
s. I am calling the requestFocus
on the second EditText
field since by default the focus goes to the first one. The focus appears to be in the second field (the second one gets the highlighted border), but if we try to enter any characters using the hardware keyboard the text appears in the first EditText
control. Any ideas why it would be happening?
It's hard to tell whether this was your problem, but it's not unlikely.
TL;DR: Never call focus-changing methods like requestFocus()
from inside a onFocusChanged()
call.
The issue lies in ViewGroup.requestChildFocus()
, which contains this:
// We had a previous notion of who had focus. Clear it.
if (mFocused != child) {
if (mFocused != null) {
mFocused.unFocus();
}
mFocused = child;
}
Inside the private field mFocused
a ViewGroup stores the child view that currently has focus, if any.
Say you have a ViewGroup VG
that contains three focusable views (e.g. EditTexts) A
, B
, and C
.
You have added an OnFocusChangeListener
to A
that (maybe not directly, but somewhere nested inside) calls B.requestFocus()
when A
loses focus.
Now imagine that A
has focus, and the user taps on C
, causing A
to lose and C
to gain focus. Because VG.mFocused
is currently A
, the above part of VG.requestChildFocus(C, C)
then translates to this:
if (A != C) {
if (A != null) {
A.unFocus(); // <-- (1)
}
mFocused = C; // <-- (3)
}
A.unFocus()
does two important things here:
It marks A
as not having focus anymore.
It calls your focus change listener.
In that listener, you now call B.requestFocus()
. This causes B
to be marked as having focus, and then calls VG.requestChildFocus(B, B)
. Because we're still deep inside the call I've marked with (1)
, the value of mFocused
is still A
, and thus this inner call looks like this:
if (A != B) {
if (A != null) {
A.unFocus();
}
mFocused = B; // <-- (2)
}
This time, the call to A.unFocus()
doesn't do anything, because A
is already marked as unfocused (otherwise we'd have an infinite recursion here). Also, nothing happens that marks C
as unfocused, which is the view that actually has focus right now.
Now comes (2)
, which sets mFocused
to B
. After some more stuff, we finally return from the call at (1)
, and thus at (3)
the value of mFocused
is now set to C
, overwriting the previous change.
So now we end up with an incosistent state. B
and C
both think they have focus, VG
considers C
to be the focused child.
In particular, keypresses end up in C
, and it is impossible for the user to switch focus back to B
, because B
thinks it already has focus and thus doesn't do anything on focus requests; most importantly, it does not call VG.requestChildFocus
.
Corollary: You also shouldn't rely on results from hasFocus()
calls while inside an OnFocusChanged
handler, because the focus information is inconsistent while inside that call.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With