Трудно сказать, была ли это ваша проблема, но это не исключено.
TL; DR: никогда не вызывать изменяющие фокус методы, такие как requestFocus()
, изнутри вызова onFocusChanged()
.
Проблема заключается в ViewGroup.requestChildFocus()
, который содержит это:
// We had a previous notion of who had focus. Clear it.
if (mFocused != child) {
if (mFocused != null) {
mFocused.unFocus();
}
mFocused = child;
}
Внутри частного поля mFocused
a ViewGroup хранит дочернее представление, которое в данный момент имеет фокус, если оно есть.
Скажем, у вас есть ViewGroup VG
, которая содержит три фокусируемых вида (например, EditTexts) A
, B
и C
.
Вы добавили OnFocusChangeListener
к A
, который (возможно, не напрямую, а где-то внутри) вызывает B.requestFocus()
, когда A
теряет фокус.
Теперь представьте, что у A
есть фокус, и пользователь нажимает на C
, в результате чего A
теряет и C
получает фокус. Поскольку VG.mFocused
в настоящее время A
, вышеуказанная часть VG.requestChildFocus(C, C)
затем переводится в:
if (A != C) {
if (A != null) {
A.unFocus(); // <-- (1)
}
mFocused = C; // <-- (3)
}
A.unFocus()
делает здесь две важные вещи:
Он помечает A
как не имеющий фокуса.
Он вызывает вашего слушателя смены фокуса.
В этом слушателе вы сейчас звоните B.requestFocus()
. Это приводит к тому, что B
помечается как имеющий фокус, а затем вызывает VG.requestChildFocus(B, B)
. Поскольку мы все еще глубоко внутри вызова, который я пометил (1)
, значение mFocused
по-прежнему A
, и, таким образом, этот внутренний вызов выглядит следующим образом:
if (A != B) {
if (A != null) {
A.unFocus();
}
mFocused = B; // <-- (2)
}
На этот раз вызов A.unFocus()
ничего не делает, потому что A
уже помечен как не сфокусированный (в противном случае у нас здесь будет бесконечная рекурсия). Кроме того, ничего не происходит, что помечает C
как несфокусированный, то есть точка зрения, что на самом деле имеет фокус прямо сейчас.
Теперь приходит (2)
, который устанавливает mFocused
в B
. После еще нескольких вещей мы наконец возвращаемся из вызова на (1)
, и, таким образом, на (3)
значение mFocused
теперь установлено на C
, перезаписывая предыдущее изменение .
Так что теперь мы в конечном итоге в незавершенном состоянии. B
и C
оба думают, что у них есть фокус, VG
считает C
сфокусированным ребенком.
В частности, нажатия клавиш заканчиваются на C
, и пользователь не может переключить фокус обратно на B
, потому что B
считает, что он уже имеет фокус и, следовательно, не делать что-либо на запросы фокуса; самое главное, он не звонит VG.requestChildFocus
.
Следствие. Вам также не следует полагаться на результаты вызовов hasFocus()
, когда они находятся внутри обработчика OnFocusChanged
, поскольку во время этого вызова информация о фокусе противоречива.