Пользовательская обработка фокуса ViewGroup

Допустим, у меня есть пользовательская ViewGroup, которая может быть сфокусирована, и имеет несколько дочерних представлений, которые также можно сфокусировать (специальное вертикальное меню для телевизионных приставок Android, которое должно реагировать на удаленный контроллер).

Мне нужно передать фокус некоторым дочерним представлениям всякий раз, когда пользовательская ViewGroup получает фокус.

Я установил descendantFocusability в beforeDescendants и установил OnFocusChangeListener в пользовательскую ViewGroup, но OnFocusChangeListener.onFocusChanged() никогда не вызывается. Похоже, beforeDescendants работает не так, как я ожидал. На самом деле установка beforeDescendants работает так же, как установка afterDescendants — фокус принимается ближайшим дочерним представлением, и пользовательская ViewGroup не имеет возможности решить, какое дочернее представление должно получить фокус.

Как я могу добиться желаемого поведения? Каковы наилучшие методы обработки фокусов в ViewGroups?


person traninho    schedule 09.04.2015    source источник
comment
Вы нашли решение своей проблемы? Ищу решение похожей проблемы. Я хочу знать, когда ViewGroup теряет фокус, т.е. дочерний вид не имеет фокуса.   -  person AlanKley    schedule 02.09.2015
comment
@AlanKley Извините за поздний ответ ... к сожалению, я не нашел решения для этого. Я решил эту проблему, установив OnFocusChangeListener для всех дочерних элементов ViewGroup и добавив логику (какой дочерний элемент должен получить фокус) внутри слушателя... Если вы найдете какое-либо решение, не стесняйтесь писать его здесь :)   -  person traninho    schedule 08.10.2015


Ответы (2)


Покопавшись в исходниках Android, я понял, как передать фокус дочерним элементам ViewGroup. Например, у меня есть собственные ViewGroup с несколькими полями EditText внутри. Когда пользователь нажимает на ViewGroup (вне каждого EditText), я хочу направить фокус на одно из полей.

Чтобы сделать это, нам нужно установить для потомкаFocusability значение FOCUS_AFTER_DESCENDANTS. Это означает, что мы можем обработать событие фокуса до того, как наше пользовательское представление попытается сфокусироваться:

setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);


Следующим шагом является переопределение метода onRequestFocusInDescendants, он будет вызываться до пользовательского представления requestFocus, если установлен флаг FOCUS_AFTER_DESCENDANTS. В этом методе мы можем запросить дочерний фокус:

@Override
protected boolean onRequestFocusInDescendants(final int dir, final Rect rect) {
    return getChildAt(this.target).requestFocus();
}

Одна важная вещь с этим методом: если мы вернем здесь true, нашему пользовательскому представлению не будет предложено сфокусироваться (только с FOCUS_AFTER_DESCENDANTS).

Таким образом, мы можем изменить поле target, чтобы изменить, какой дочерний элемент будет сфокусирован при нажатии ViewGroup.


Для лучшего понимания вы можете найти requestFocus метод в ViewGroup в исходниках Android.

person Kirill    schedule 08.10.2017

У меня аналогичная проблема. В нашем приложении Android TV мы хотим выбрать последнюю сфокусированную позицию нашего пользовательского меню (это пользовательский вертикальный LinearLayout).

Вы должны переопределить метод addFocusables(views: ArrayList<View>, direction: Int, focusableMode: Int) . Этот метод вызывается, когда ViewRootImpl выполняет поиск всех доступных фокусируемых представлений. Флаги FOCUS_AFTER_DESCENDANTS и FOCUS_BEFORE_DESCENDANTS определяют только позицию вставки родительского представления в фокусируемые представления. В случае FOCUS_BEFORE_DESCENDANTS ваше пользовательское представление будет добавлено перед вашим дочерним элементом. В случае FOCUS_AFTER_DESCENDANTS ваш собственный вид будет добавлен после вашего дочернего элемента.

Логика переопределения состоит в том, чтобы добавить в список только ваше собственное представление, когда раньше у вас не было сфокусированного дочернего элемента.

override fun addFocusables(views: ArrayList<View>, direction: Int, focusableMode: Int) {
    // if we did have focused child before, we must add only parent view
    // otherwise our child already has focus, and we don't wont to change focus behavior
    if (focusedChild == null) {
        views.add(this)
    } else {
        super.addFocusables(views, direction, focusableMode)
    }
}

Вы также должны переопределить requestFocus(direction: Int, previouslyFocusedRect: Rect), потому что вы должны передать фокус своему ребенку или решить, что не можете этого сделать.

override fun requestFocus(direction: Int, previouslyFocusedRect: Rect): Boolean = 
        getViewToFocus()?.requestFocus() ?: false

В этом примере getViewToFocus() возвращает дочерний элемент, который я хочу сфокусировать, или null, если у нас нет дочернего элемента.

private fun getViewToFocus() =
        when {
            lastSelectedPos in 0..childCount -> getChildAt(lastSelectedPos)
            isNotEmpty() -> getChildAt(0)
            else -> null
        }

Я использую Kotlin в примерах, но вы также можете использовать java. Наслаждаться!

person karenkov_id    schedule 07.02.2020
comment
Чтобы сосредоточиться на Android TV, я рекомендую jianshu.com/p/bc7b3878f140 - person tmm1; 10.06.2020