工作记录:RecyclerView获取子View、Item错乱以及禁用RecyclerView滑动的问题

前言

在最近的项目中,有这样一个需求:在RecyclerView通过ID查询到指定Item,然后滚动视图到指定Item,并对视进行呼吸灯闪烁显示。一种快速查询定位的需求。
首选我想尝试使用RecyclerView中Item动画去实现,无果。于是就通过使用获取指定Item的View然后对View施加动画实现这种效果。
我相信使用RecyclerView动画应该能更好的实现这种效果,但我并未涉足过这块,项目中也没有充足的时间去了解,若有更好的实现方式请大家留言评论,让我学习一下。
接下来结合我的实现方式记录一下期间遇到坑。

一 RecyclerView getChildAt()异常

异常分析

遇到的第一个麻烦就是recyclerview.getChildAt(pos)获取View之后的操作报空指针异常(java.lang.NullPointException),因为通过getChildAt(pos)方法返回null.这个问题比较好解决,是因为粗心造成的——RecyclerView并不会为每一个Item创建一个View,RecyclerView的缓存机制只会维护一部分View,通过回收离屏Item的View给入屏Item复用的方式实现展示效果(详情请阅读RecyclerView与ListView 对比浅析:缓存机制)。所以如果所要获取的pos不在当前屏幕内显示,它是没有View实体存在的,必然返回空。

解决办法

解决办法很简单:首先让recyclerview滑动,使得要获取的Item出现在屏幕内,让后再去获取
此处有坑,不能直接用指定Item的pos去获取View,因为这个pos标注的是Item在RecyclerView中排列的位置,而不是Item在RecyclerView所呈现视图中的位置

image

如图:Item的pos对应图中红色的数字,而要使用recyclerview.getChildAt(pos)获取RecyclerView的子View,应该使用图中绿色的角标。那么问题来了,怎么获取pos对应的绿色角标值呢?
结合上文中我们的前提条件recyclerview滑动,使得要获取的Item出现在屏幕内。在我们需要的View显示在屏幕的前提下,我们可以首先获取屏幕内显示的第一个Item在整个RecyclerView中排列的位置,通过计算目标Item和屏幕内第一个Item的位置角标差值,就可以知道目标Item处在屏幕中的第几个。代码实现如下:

LinearLayoutManager manager = (LinearLayoutManager) recyclerview.getLayoutManager();
int first = manager.findFirstVisibleItemPosition();//获取第一个显示的Item的pos
int acutalPos = (position - first) >= 0 ? position - first : 0;//计算差值确定位置
View childAt = recyclerview.getChildAt(acutalPos);//获取目标Item对用的View

二 改变Item颜色导致其它Item颜色错乱

通过问题一种的方案,我成功的拿到了目标Item进行修改背景的操作,一切看起来都是那么的正常。但是当我在执行背景操作,也就是动画闪烁的时候,细心的我滑动了一下RecyclerView。哇靠,什么情况,无数的Item的背景颜色被改变了。刚开始我还以为是pos错乱,或者滑动监听出现了问题,导致其他Item也执行了闪烁动画。但细心的我发现,这些View的背景颜色并不是被执行动画了,而是被”永久“的改变了颜色。而且只在执行动画时进行滑动才会出现——一定又是缓存机制搞得鬼!!!
没错,就是RecyclerView在复用View时带来的颜色改变:
当我们对目标View进行动画时,它的背景颜色被动态的改变。加入在某一时刻,它的背景被改为红色。而这个时候我们进行滑动,目标View离开屏幕。这个View在背景为红色的情况下被回收。紧接着新入屏的Item会复用这个View,从此一生无法摆脱这个喜庆的背景了!!!
解决方案很简单:在执行动画的时候禁止滑动,动画执行结束之后再解锁。这就带来了第三个问题。

三 禁止RecyclerView滚动

怎么禁止RecyclerView滚动呢?
第一个想到:setEnabled()幸运的是,这是无效的。
查看RecyclerView的方法:setNestedScrollingEnabled没用,这是解决嵌套滑动的。
想着可以通过重写RecyclerView的OnTouch事件解决。但是工程量太大了,事件不允许啊!还要加一些回调,麻烦。慢慢的看着RecyclerView的源码,当addOnItemTouchListener()跃入我的眼帘。灵机一闪,如果让RecyclerView的子View拦截事件呢?
当执行动画的时候让子View拦截所有事件,动画结束的时候再解锁拦截!代码如下:

//创建一个OnItemTouchListener,拦截所有的Touch事件。但不执行任何后续操作
RecyclerView.OnItemTouchListener disabler = new RecyclerView.OnItemTouchListener() {

            @Override
            public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
                //拦截事件
                return true;
            }

            @Override
            public void onTouchEvent(RecyclerView rv, MotionEvent e) {

            }

            @Override
            public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {

            }
        };
//在动画开始执行得到时候添加监听
recyclerview.addOnItemTouchListener(disabler);
//在动画执行完成后释放监听
mBinding.recyclerview.removeOnItemTouchListener(disabler);

总结

上面就是我在工作中遇到的问题之一,才疏学浅,如有更好的解决办法还望不吝赐教,留言告知!

你可能感兴趣的:(工作记录:RecyclerView获取子View、Item错乱以及禁用RecyclerView滑动的问题)