我们是在使用Recyclerview的时候,往往会想着搞一些比较用户交互体验比较好的效果,拖拽或者删除等等。
在android5.0之后android v7包下提供了ItemTouchHelper,供我们开发者实现这些效果。
这里就不再详细赘述itemTouchHelper如何使用了,网上有很多好的文章介绍,
这里就推荐一篇https://www.jianshu.com/p/e3426dcc8ef1,作者GitLqr。写的很不错。
我主要是在对于ItemTouchHelper使用过程中遇到的几个问题记录一下。
第一个问题:
我们在拖拽item交换位置后,再次点击item获得对应的position的数据不正确。
第二个问题:
是根据修复第一问题后产生的,具体问题是拖拽后重新刷新position后,会再次刷新
被拖拽item与之交互的item,会产生刷新动画的效果。
不知道如何插入视频,本来是录了一个,查了很多资料却貌似csdn现在不支持了。
给一个视频链接吧http://www.56.com/u82/v_MTU5ODIzOTEx.html。我录了一下放到这上面了。
从视频中我们看到这种刷新效果很不友好。
对于上述两个问题我们来一一解决。
对于第一个问题,首先看代码:
//一个拖拽接口
public interface ItemMoveListener {
boolean onItemMove(int fromPosition,int toPosition);
}
然后在ItemTouchHelper.CallBack的回调方法onMove中调用
/**
* 当item拖拽移动时触发
* @param recyclerView
* @param viewHolder
* @param viewHolder1
* @return
*/
@Override
public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder viewHolder1) {
return mItemMoveListener.onItemMove(viewHolder.getAdapterPosition(),viewHolder1.getAdapterPosition());
}
然后在我们的adapter中实现onItemMove
@Override
public boolean onItemMove(int fromPosition, int toPosition) {
Collections.swap(list,fromPosition,toPosition);
notifyItemMoved(fromPosition,toPosition);
return true;
}
我们在实现类中onItemMove方法中调用
Collections.swap(list,fromPosition,toPosition); 更改list中开始和结尾position的位置
notifyItemMoved(fromPosition,toPosition);刷新从开始到结尾position 的位置。
到这里没有毛病。但是我们可以在拖拽后,然后点击拖拽后的item,在Logcat控制台打印一下所点击item对应的数据,你会发现
已经不正确的,这是因为你在拖拽的时候,每当拖拽的item被拖拽到最邻近的item旁边时,就会和邻近的item触发onMove方法,进而调用了onItemMove方法,进行了position相互。然后再拿着互换后的position(被拖拽的item)再与下一个即将邻近的item的position进行互换。如图
不能上传视频是一大遗憾,若是知道如何上传,请老铁们留个言,指导一下。
我们拖拽后点击事实上position的值已经与list中变化后的position不对应了,所以在获得item中的数据的时候list.get(position).xx已经不正确了。
从图中我们可以看到,事实上item(position)总是与之最近的item进行位置交换,随之position进行交换。
那么我们要做就是两种要么就是对这两个交换的item交换后再把position的位置交换回来,第二种就是记录拖拽的item的positon和最后一个被交换的item 的positon,然后把这两个item之间(包含这个两个)的item依次再刷新交换后重新排列后的position的值。
显然第一种操作的频率很高(若是拖拽之后,经过的item很多),而且实践证明并不能把正确的positon展现出来。
第二种是我用的。
我是先记录一下拖拽item的positon的值firstPositon和最后一个被交换item的positon的值endPosition。
@Override
public boolean onItemMove(int fromPosition, int toPosition) {
if(isFirst){
firstPosition=fromPosition;
isFirst=false;
}
endPosition=toPosition;
Collections.swap(list,fromPosition,toPosition);
notifyItemMoved(fromPosition,toPosition);
return true;
}
然后写了一个监听回调,在拖拽动作完成的时候调用该回调方法
/**
* 当item的交互动画结束时触发
* @param recyclerView
* @param viewHolder
*/
@Override
public void clearView(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
super.clearView(recyclerView, viewHolder);
viewHolder.itemView.setBackgroundColor(viewHolder.itemView.getContext().getResources().getC
mIteClearListener.itemClearViewAction();
}
然后在adpter中实现该接口。
@Override
public void itemClearViewAction() {
isFirst=true;//释放状态
notifyItemRangeChanged(Math.min(firstPosition, endPosition), Math.abs(firstPosition - endPosition) +1);
}
这里的
notifyItemRangeChanged(int positionStart, int itemCount)Android已经很好的提供给我们了,使用即可。
ok,到这里第一个问题就解决了。
看第二个问题,也是在第一个问题的基础上产生的。上面有录的视频地址,里面可以清晰的看到调用notifyItemRangeChanged后
被刷新到的item会都会有一个刷新的效果,很很友好,一般我们想要的结果是拖拽item刷新一下即可,提示用户,你拖拽的item被你拖拽到这里了。
在这里既然是刷新的效果,必然想到是recyclerview自身的,于是我就问了度娘,
recycler.getItemAnimator().setChangeDuration(0);
找到了这个方法,使刷新的动效的时间我设置为0。
于是运行了,发现很完美的解决了我的问题,而且动效只会在拖拽的item中上面有效果,这更是我想要的,但是我没有找到为何最后一个会有刷新的效果,按上面的设置,应该是所以的刷新效果都没有才对,却不知为何,这里埋下了伏笔,今晚估计睡不着了。有知道的伙伴,可以留言指导一下。