Ответ 1
Трудно сказать, была ли это ваша проблема, но это маловероятно.
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
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
считает, что он уже имеет фокус и, следовательно, t делать что-либо в запросах фокуса; самое главное, он не вызывает VG.requestChildFocus
.
Следствие. Вы также не должны полагаться на результаты вызовов hasFocus()
, находясь внутри обработчика OnFocusChanged
, потому что информация о фокусе несовместима во внутреннем вызове.