自定义ViewGroup焦点处理

tra*_*nho 19 android focus android-custom-view viewgroup android-view

假设我有一个可自由聚焦的自定义ViewGroup,并且有一些可以聚焦的子视图(Android机顶盒的自定义垂直菜单应该在远程控制器上作出反应).

每当自定义ViewGroup获得焦点时,我都需要将焦点传递给一些子视图.

我设置descendantFocusabilitybeforeDescendants并设置OnFocusChangeListener为自定义ViewGroup但OnFocusChangeListener.onFocusChanged()从未调用.它看起来像beforeDescendants我预期的那样不起作用.实际设置beforeDescendants工作与设置相同afterDescendants- 焦点由最近的子视图拍摄,自定义ViewGroup没有机会决定哪个子视图应该关注焦点.

我怎样才能达到理想的行为?在ViewGroups中处理焦点的最佳实践是什么?

Kir*_*ill 5

深入研究Android资料后,我了解了如何将重点分配给ViewGroup的孩子。例如,我有ViewGroup几个EditText内部字段的自定义。当用户单击ViewGroup(在每个窗口之外EditText)时,我想将焦点分配到其中一个字段。

为此,我们需要将DescendantFocusability设置为FOCUS_AFTER_DESCENDANTS。这意味着我们可以我们的自定义视图尝试聚焦之前处理焦点事件:

setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
Run Code Online (Sandbox Code Playgroud)


下一步是重写onRequestFocusInDescendants方法,将在设置自定义视图的requestFocusif标志之前调用该方法FOCUS_AFTER_DESCENDANTS。在这种方法中,我们可以请求子级焦点:

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

使用此方法的重要一件事:如果返回true此处,则不会要求我们的自定义视图集中显示(仅使用FOCUS_AFTER_DESCENDANTS)。

通过这种方式,我们可以更改target字段以更改ViewGroup单击时将重点关注哪个孩子。


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


小智 5

我有类似的问题。在我们的 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)
    }
}
Run Code Online (Sandbox Code Playgroud)

您还必须推翻,requestFocus(direction: Int, previouslyFocusedRect: Rect)因为您必须将注意力转移给您的孩子或决定您不能这样做。

override fun requestFocus(direction: Int, previouslyFocusedRect: Rect): Boolean = 
        getViewToFocus()?.requestFocus() ?: false
Run Code Online (Sandbox Code Playgroud)

在此示例中, getViewToFocus() 返回我想要聚焦的子项,如果我们没有任何子项,则返回 null

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

我在示例中使用 Kotlin,但您也可以使用 java。享受!