Android TV问题:焦点移至ScrollView可视区域外的View
解决ScrollView可视区外View意外获取焦点的问题
我之前也碰到过一模一样的情况!这本质上是Android默认焦点查找逻辑的“盲区”——它只依据视图的布局坐标判断是否在同一焦点切换路径上,完全忽略了视图是否处于ScrollView的可视范围内。下面是几个经过验证的解决思路,你可以根据自己的场景选择:
1. 自定义ViewGroup,重写焦点查找逻辑
最可靠的方式是给ScrollView内的垂直ViewGroup自定义焦点搜索规则,过滤掉不可视的子View。比如重写focusSearch方法,每次查找下一个焦点时,先检查目标View是否在ScrollView的可视区域内,不在的话就跳过继续找:
class VisibilityAwareFocusViewGroup(context: Context, attrs: AttributeSet?) : LinearLayout(context, attrs) { override fun focusSearch(focused: View, direction: Int): View? { var nextCandidate = super.focusSearch(focused, direction) val scrollView = findAncestorScrollView() while (nextCandidate != null && scrollView != null) { val scrollVisibleRect = Rect() scrollView.getDrawingRect(scrollVisibleRect) val candidateRect = Rect() nextCandidate.getDrawingRect(candidateRect) // 把候选View的坐标转换到ScrollView的坐标系下 scrollView.offsetDescendantRectToMyCoords(nextCandidate, candidateRect) // 如果候选View和ScrollView可视区域没有交集,继续查找下一个 if (!Rect.intersects(scrollVisibleRect, candidateRect)) { nextCandidate = super.focusSearch(nextCandidate, direction) } else { break } } return nextCandidate } private fun findAncestorScrollView(): ScrollView? { var parent = parent while (parent != null && parent !is ScrollView) { parent = parent.parent } return parent as ScrollView? } }
把原来的垂直ViewGroup替换成这个自定义类,就能让焦点只在可视区内切换了。
2. 监听ScrollView滚动,动态切换子View的可聚焦状态
这个方法更简单直接:监听ScrollView的滚动事件,实时判断每个子View是否在可视区内,不在的话就临时设置它不可聚焦:
scrollView.viewTreeObserver.addOnScrollChangedListener { val scrollTop = scrollView.scrollY val visibleBottom = scrollTop + scrollView.height val verticalContainer = scrollView.findViewById<ViewGroup>(R.id.your_vertical_container) // 遍历子View,更新可聚焦状态 for (i in 0 until verticalContainer.childCount) { val child = verticalContainer.getChildAt(i) val childTop = child.top val childBottom = child.bottom val isVisible = childBottom > scrollTop && childTop < visibleBottom child.isFocusable = isVisible child.isFocusableInTouchMode = isVisible } }
注意:如果子View数量很多,频繁遍历可能会有性能问题,建议只处理当前焦点附近的几个View,或者给子View添加Tag分组批量处理。
3. 用RecyclerView替代ScrollView(最优解)
如果你的垂直列表项是可以复用的,直接换成RecyclerView是最省心的方案。RecyclerView的视图回收机制会自动把不可视的子View从布局中detach,这些被回收的View自然不会参与焦点查找,从根源上解决了问题。而且RecyclerView本身就支持焦点管理的优化,不需要额外写太多代码。
内容的提问来源于stack exchange,提问作者Tor Langballe
相关产品推荐
相关产品推荐

