View可见性感知

产品需求:视频view感知到页面生命周期的自动播放或暂停,同时自动数据上报曝光等。

技术需求:感知到view的展示,移除,覆盖等场景,而且要在view内部实现。

技术原理:

检测 View 可见性
主要从下面几个方面来考虑了:

View 的加载过程监控
View 是否在屏幕中的检测
View 是否被覆盖的检测
View 的加载过程监控
view 的加载过程,就是 view 被添加到 window 的过程,很容易的就想到了两个方法 onAttachedToWindow和onDetachedFromWindow,同时还有另外的方法onWindowVisibilityChanged,

onAttachedToWindow和 onDetachedFromWindow
这两个方法分别是 view 被添加到 window 时,以及从 window 删除时的回调。监控这两者能很好的知道 view 被添加以及从 window 删除的时间,但是这两个方法在 view 的整个生命周期中只有一次调用,仅仅是这两者来监控是肯定不行的。

onWindowVisibilityChanged
onWindowVisibilityChanged 其实和上面两个方法有所关联,查看源码可知:

void dispatchAttachedToWindow(AttachInfo info, int visibility) {
    ...
    onAttachedToWindow();
    int vis = info.mWindowVisibility;
    if (vis != GONE) {
         onWindowVisibilityChanged(vis);
    }
}

void dispatchDetachedFromWindow() {
    ...
    if (info != null) {
        int vis = info.mWindowVisibility;
        if (vis != GONE) {
            onWindowVisibilityChanged(GONE);
        }
    }
    onDetachedFromWindow();
}

所以上面的两个选择其一来监控就行了。

然后就是 View 的是否在屏幕中的检测以及是否被遮挡的检测了。

View 的是否在屏幕中的检测
关于是否在屏幕中的检测主要是通过 getGlobalVisibleRect()方法来实现的。同时 view 还提供了另外一个方法getLocalVisibleRect()。这两个方法都能用来检测 view 是否在屏幕中,他们的区别在于:

getGlobalVisibleRect 得到的 Rect 是当前在屏幕内的 View 的区域,其坐标是相对于整个窗口而言的;

public boolean getGlobalVisibleRect(Rect r, Point globalOffset) {
        int width = mRight - mLeft;
        int height = mBottom - mTop;
        if (width > 0 && height > 0) {
            r.set(0, 0, width, height);
            if (globalOffset != null) {
                globalOffset.set(-mScrollX, -mScrollY);
            }
            return mParent == null || mParent.getChildVisibleRect(this, r, globalOffset);
        }
        return false;
    }
    
getLocalVisibleRect 得到的 Rect 是当前在屏幕内的 View 区域的,其坐标是相对于当前 view 本身的;

public final boolean getLocalVisibleRect(Rect r) {
        final Point offset = mAttachInfo != null ? mAttachInfo.mPoint : new Point();
        if (getGlobalVisibleRect(r, offset)) {
            r.offset(-offset.x, -offset.y); // make r local
            return true;
        }
        return false;

}


另外如果 View 在当前屏幕内,则两者都返回 true,如果不在则返回 false,而且如果在屏幕外两者得到的 Rect 的坐标内容是一样的。可以通过这两个方法的任意一种来获取是否在屏幕中,并且计算当前显示出来的面积。

//检测是否被遮挡,如果被遮挡或者不在屏幕中则返回true
public boolean isViewCovered(final View view) {
        if(view == null || !isViewValid) {
            LogTool.d(TAG,"isViewCovered view == null");
            return true;
        }
        View currentView = view;
        Rect currentViewRect = new Rect();
        boolean partVisible = currentView.getGlobalVisibleRect(currentViewRect);
        // 获取可见区域为false,说明被覆盖了
        if(!partVisible){
            LogTool.d(TAG,"isViewCovered partVisible is false");
            return true;
        }
        //可见区域小于view区域的50%
        if(currentViewRect.width()*currentViewRect.height() < view.getMeasuredWidth()*view.getMeasuredHeight()/2){
            LogTool.d(TAG,"isViewCovered Visible rect is smaller than 50% area!");
            return true;
        }  else {

            return false;

        }
    }
基于以上的代码可以有效的判断出当前 View 是否或者是否在屏幕中,但是有些时候即使是在屏幕中,当前的 View 也可能被其他的 View 遮挡,这个时候就要做第三步了。

View是否被覆盖的检测
关于 View 是否被其他 View 遮挡的问题,貌似只有一种解决方案——循环查找父级 View 以及兄弟 View 然后判断两者的 Rect 区域是否有交际。

最终代码如下:

public boolean isViewCovered(final View view) {
        if(view == null || !isViewValid) {
            LogTool.d(TAG,"isViewCovered view == null");
            return true;
        }
        View currentView = view;
        Rect currentViewRect = new Rect();
        boolean partVisible = currentView.getGlobalVisibleRect(currentViewRect);
        // 获取可见区域为false,说明被覆盖了
        if(!partVisible){
            LogTool.d(TAG,"isViewCovered partVisible is false");
            return true;
        }
        //可见区域小于view区域的50%
        if(currentViewRect.width()*currentViewRect.height() < view.getMeasuredWidth()*view.getMeasuredHeight()/2){
            LogTool.d(TAG,"isViewCovered Visible rect is smaller than 50% area!");
            return true;
        }

        ViewGroup currentParent;
        Rect viewRect, otherViewRect;
        View otherView;
        while (currentView.getParent() instanceof ViewGroup) {
            currentParent = (ViewGroup) currentView.getParent();
            // if the parent of view is not visible,return true
            if (currentParent.getVisibility() != View.VISIBLE){
                LogTool.d(TAG,"isViewCovered Visible currentParent is not visible!");
                return true;
            }
            int start = indexOfViewInParent(currentView, currentParent);
            for (int i = start + 1; i < currentParent.getChildCount(); i++) {
                viewRect = new Rect();
                view.getGlobalVisibleRect(viewRect);
                otherView = currentParent.getChildAt(i);
                otherViewRect = new Rect();
                otherView.getGlobalVisibleRect(otherViewRect);
                // if view intersects its older brother(covered),return true
                if (Rect.intersects(viewRect, otherViewRect)){
                    LogTool.d(TAG, "isViewCovered view intersects its older brother(covered)");
                    return true;
                }
            }
            currentView = currentParent;
        }
        return false;
    }

 

上面的方法就能很好的检测当前 View 是否在屏幕中,以及是否被其他 view 所遮挡。当然这个是根据我的需求写出来的代码,如果有不同的需求,可以稍作修改,因为基本原理就在这儿。

接下来就是检测的时机问题了。

检测 View 可见性的时机
因为需求的原因,项目中需要在适当的时机来检测当前 view 是否可见,可能引起 view 可见性的因素有:

listVie、ScrollView 的滑动
viewPager 切换
fragment 切换
Activity 切换
目前能想到的就这些,如果还有其他的欢迎大家补充。其中第四个好说,因为它会导致 view.onDetachedFromWindow 被调用。主要是前面三个,在 fragment show\hint 切换过程中不会触发任何 view 的变化,包括滑动等。

至少目前我没有找到一个能够在 view 中被动监听由系统通知的 view 状态切换监听。没办法只能用笨方法——由上层的这些控件在状态变化时主动通知广告View 来进行可见性检测。一般在 listView、OnScrollView 的滑动监听,以及 Fragment 的 onHintChange 中等。只是调用一下方法,成本不算太高还算能接受。

好了,以上就是关于 Android 中 View 的可见性检测的所有内容,关于是最后的监听方面,如果您有好的方案烦请不吝指教。

参考
判断view 是否被遮挡
View 源码
————————————————
版权声明:本文为CSDN博主「耳东_」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/u011494285/article/details/81350719

 

java版本资料及详细讲解:

https://blog.csdn.net/aikongmeng/article/details/72373748

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值