判断RecyclerView 点击的item中的View是否完全显示,如不完全显示,滑动RecyclerView将View完全显示(仿飞聊app图片查看退场动画方案)

好久不写文章了,工作上遇到一个小问题,自己写的图片查看库,在图片被遮挡时,回退的动画有些瑕疵,究其原因是因为遮挡后坐标拿不到了,原来图片查看效果,
原来退场遮挡时会有抖动.gif

然后正好最近飞聊app比较火,主要是我们产品抄了很多头条系app的功能让我也注意到了这个app,看了一下飞聊app 的图片查看,发现他们的在图片放大查看时,做类似微信的交互,回退时,图片的位置改了(其实是recyclerView滑动了)


飞聊图片退场前会滑动recycleview让图片显示全.gif

不禁惊叹,哇,还可以这样操作,如果让图片全部显示全,那么退场时图片坐标都可以一一对应,退场动画也就不会抖动了,OK,有了思路我们开始实现
首先图片可以点击,那么item一定是可见的,我们需要知道imageView getTop 的距离是不是小于0,来确定顶部遮挡,以及getBottom的距离是不是大于RecyclerView的高度 来确定底部遮挡,
想的太简单.png

但是在我写完debug时,拿到的距离无论是getTop还是getBottom都是固定的数,列表滑动再点击,拿到的数也不会改,纳尼?这是什么鬼,(后来发现其实是我们基类组件Adapter封装的有问题,正常情况下是可以拿到的)


getTop和getY拿到的值始终是固定的

看来这个方案是不行了,于是我把所有view获取坐标的方法都写上打印出来,看看能不能找出规律,果不其然。。。
重点看getLocalVisibleRect

先复习一下这几个View获取坐标的方法
View.getGlobalVisibleRect()

这个方法会返回一个View是否可见的boolean值,同时还会将该View的可见区域left,top,right,bottom值保存在一个rect对象中,以屏幕左上角为原点

View.getLocalVisibleRect()

这个方法和getGlobalVisibleRect有些类似,也可以拿到这个View在屏幕的可见区域的坐标,唯一的区别getLocalVisibleRect(rect)获得的rect坐标系的原点是View自己的左上角,而不是屏幕左上角。其也会调用getGlobalVisibleRect()方法

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;
    }

好了,我们先看正常图片未被遮挡时ImageView 的坐标依次是 Rect(int left, int top, int right, int bottom), 通过getLocalVisibleRect 得到
而ImageView的高度等于right坐标的绝对值,等于bottom坐标都是996(这个值好诡异)


正常情况的坐标.png

上面被遮挡时的坐标 【左 上 右 下】Rect(int left, int top, int right, int bottom)


上面被遮挡时的坐标.png

发现top的值变了,其余坐标还是原来样子,
下面被遮挡时的坐标 Rect(int left, int top, int right, int bottom)
下面被遮挡时的坐标.png

发现和正常情况相比 bottom的坐标变了,发现了规律

1,正常情况下left 和 top 坐标都是0,0 代表我们图片头部上半部分 是始终可见的
2,上面被遮挡时图片的top坐标会增加,其余坐标不会变
3,下面被遮挡时图片的bottom坐标会减少,其余坐标不会变

按照这个规律,那么可以得出下面代码

 /***
     * 解决列表图片被遮挡时 RecyclerView 滑动一段距离 显示出完整的预览图
     * 根据getLocalVisibleRect 相对view自身坐标计算偏移量 只要top和left都是0 right等于view宽度 bottom等于view高度 即图片没有被隐藏
     * 上面被隐藏 top肯定大于0 滑动偏移量即为-top
     * 下面被隐藏 bottom肯定小于 view高度 偏移量即为view高度-bottom
     *
     * 参照 飞聊app 解决方案
     */
    public WBPreviewBuilder setFixHideForRecyclerView(RecyclerView recyclerView, ImageView imageView) {
        if (null != recyclerView && null != imageView) {
            int height = imageView.getHeight();
            Rect rect = new Rect();
            imageView.getLocalVisibleRect(rect);
            if (rect.top > 0 && rect.left == 0 && rect.bottom == height) {
                //上面被遮挡
                int offset = rect.top;
                recyclerView.scrollBy(0, -offset);
            } else if (rect.top == 0 && rect.left == 0 && rect.bottom < height) {
                //下面被遮挡
                int offset = height - rect.bottom;
                recyclerView.scrollBy(0, offset);
            }
        }
        return this;
    }
1,即如果top坐标大于0,并且left坐标等于0,并且bottom坐标和图片高度相等,这时肯定是图片上半部分被遮挡了,我们需要让recyclerView滑动一段距离让view全部显示,这个偏移量也特别好计算 就是top的坐标,但是需要注意因为是图片上半部分被遮挡,我们滑动recyclerView需要是负的值,即
 int offset = rect.top;
 recyclerView.scrollBy(0, -offset);
2,如果left坐标和top坐标都是0,并且bottom坐标小于图片高度,那么这时图片肯定是下半部分被遮挡了,这时滑动的偏移量即

view的高度减去 bottom坐标

 int offset = height - rect.bottom;
 recyclerView.scrollBy(0, offset);

上面我是计算的ImageView 这个方法可以扩展一下,来计算View是否被遮挡,遮挡的话,计算遮挡距离,然后滑动RecycleView


很方便也很使用的计算方式.png

改进后的效果


改进后的效果.gif

OK,我在项目中试了一下,完美解决问题,希望有类似需求的小伙伴可以参考这种方法,解决你的问题,周末愉快~

推荐阅读更多精彩内容