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.