Android 如何获取被上层View遮挡的所有下层View?
实现思路
可以实现,核心逻辑分为两步:
- 第一步筛选出视图树中Z轴层级低于目标上层View的所有候选View
- 第二步过滤掉不可见的候选View,再判断候选View的可见显示区域是否和上层View的显示区域存在重叠,满足条件的就是被遮挡的View
Android 实现代码
Android的视图绘制顺序和父容器ViewGroup的getChildAt返回顺序完全对应,后添加的子View层级更高,会覆盖先添加的子View。
fun getOccludedViews(topView: View): List<View> { val parent = topView.parent as? ViewGroup ?: return emptyList() val topViewPosition = parent.indexOfChild(topView) val occludedList = mutableListOf<View>() val topViewRect = Rect() topView.getGlobalVisibleRect(topViewRect) val tempRect = Rect() // 仅遍历层级低于上层View的子View for (i in 0 until topViewPosition) { val child = parent.getChildAt(i) // 过滤不可见元素 if (child.visibility != View.VISIBLE || child.alpha <= 0f) continue // 判断区域是否重叠 if (child.getGlobalVisibleRect(tempRect) && Rect.intersects(topViewRect, tempRect)) { occludedList.add(child) } } return occludedList }
如果需要扫描整个应用的视图树而非仅同父容器下的View,递归遍历根视图DecorView的所有子节点,筛选z属性小于上层View的元素做重叠判断即可。
iOS 实现代码
iOS的superview.subviews数组顺序对应视图Z轴顺序,数组越靠后的元素层级越高。
func getOccludedViews(topView: UIView) -> [UIView] { guard let superview = topView.superview, let topIndex = superview.subviews.firstIndex(of: topView) else { return [] } let topFrame = topView.convert(topView.bounds, to: nil) var occludedList = [UIView]() // 遍历层级低于上层View的子View for i in 0..<topIndex { let child = superview.subviews[i] // 过滤不可见元素 guard !child.isHidden, child.alpha > 0, child.window != nil else { continue } let childFrame = child.convert(child.bounds, to: nil) // 判断区域是否重叠 if topFrame.intersects(childFrame) { occludedList.append(child) } } return occludedList }
注意事项
上述实现是基于区域重叠的判断,如果上层View存在大面积透明区域,需要精准判断是否被非透明部分遮挡,需要额外做像素级碰撞检测,性能开销会更高,常规业务场景下区域判断的精度已经足够使用。
内容的提问来源于stack exchange,提问作者Ravi H
相关产品推荐
相关产品推荐

