自定义ViewGroup的焦点处理

23
假设我有一个自定义的ViewGroup,它具有可聚焦性,并且有一些子视图也具有可聚焦性(Android机顶盒的自定义垂直菜单,应该对远程控制器做出反应)。
当自定义ViewGroup获得焦点时,我需要将焦点传递给某些子视图。
我将descendantFocusability设置为beforeDescendants,并将OnFocusChangeListener设置为自定义ViewGroup,但OnFocusChangeListener.onFocusChanged()从未被调用。看起来beforeDescendants不像我预期的那样工作。实际上,将beforeDescendants设置与设置afterDescendants相同-焦点由最近的子视图接管,自定义ViewGroup没有机会决定哪个子视图应该获得焦点。
我该如何实现所需的行为?在ViewGroups中处理焦点的最佳实践是什么?

1
你找到解决问题的方法了吗?我正在寻找类似问题的解决方案。我想知道当ViewGroup“失去”焦点时,即没有子视图拥有焦点。 - AlanKley
@AlanKley 很抱歉回复晚了...不幸的是我没有找到任何解决方案。我通过将OnFocusChangeListener设置为ViewGroup的所有子项并在监听器中编写逻辑(哪个子项应该获得焦点)来“解决”了这个问题...如果您找到任何解决方案,请随时在此处写下 :) - traninho
2个回答

9
在深入研究Android源代码后,我了解到如何将焦点分派给ViewGroup的子项。例如,我有一个自定义的ViewGroup,里面有几个EditText字段。当用户点击ViewGroup(在每个EditText之外)时,我想将焦点分派给其中一个字段。
为此,我们需要将descendantFocusability设置为FOCUS_AFTER_DESCENDANTS。这意味着我们可以在自定义视图尝试聚焦自身之前处理焦点事件:
setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);


下一步是覆盖 onRequestFocusInDescendants 方法,如果标志 FOCUS_AFTER_DESCENDANTS 被设置,它将在自定义视图的 requestFocus 之前被调用。在这个方法中,我们可以请求子焦点:

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

这种方法的一个重要事项是:如果我们在此处返回true,则我们的自定义视图将不会被请求聚焦(仅使用FOCUS_AFTER_DESCENDANTS)。

通过这种方式,我们可以更改target字段以更改单击ViewGroup时将聚焦哪个子元素。


为了更好地理解,您可以在Android源代码中的ViewGroup中找到requestFocus方法。


5

我有类似的问题。在我们的 Android 电视应用程序中,我们想要选择自定义菜单(它是自定义垂直 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。享受吧!


针对Android TV的关注,我推荐https://www.jianshu.com/p/bc7b3878f140。 - tmm1

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接