Focus issue with multiple EditTexts
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 hap开发者_高级运维pening?
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.
I found the solution.
Inside onFocusChange don't call directly requestFocus, instead post a runnable to request focus. e.g. below my code,
editText.setOnFocusChangeListener(new OnFocusChangeListener() {
public void onFocusChange(View v, boolean hasFocus) {
if (hasFocus == false) {
editText.post(new Runnable() {
@Override
public void run() {
editText.requestFocus();
}
});
}
}
});
I faced the same problem, one of my EditText has an OnFocusListener and when it lose focus I do some transformations, but if something goes wrong I try to requestFocus again and let the user to fix the problem. Here's when the problem shows up, I end up with to EditText with focus, I tried to search the view with the focus but the findFocus() returned null. The only solution I found was to create a EditText variable
private EditText requestFocus;
If any problem, I set my EditText to that variable and here is what I don't like but it works, I set OnFocusListener to the other views on my activity. When they gain focus, I do this
@Override
public void onFocusChange(View v, boolean hasFocus) {
if (hasFocus)
if(requestFocus != null){
v.clearFocus();
requestFocus.requestFocus();
requestFocus = null;
}
}
I clear the focus from the view that has it, I request Focus and set the variable requestFocus null.
I believe that this is happening because at the time I request a Focus no one has a focus yet, my EditText regain the focus and the the activity gives focus to the next view. Hope it helps to someone.
Try using requestFocus(); http://developer.android.com/reference/android/view/View.html#requestFocus()
精彩评论