背景:
一般来讲,是用不到这个需求的,但是,今天在搞层叠卡片的时候,碰到这个问题,我希望的是只有顶层item可以滑动,其他item禁止滑动。否则,用户拖拽的时候,拖拽了非顶层卡片,就会出现错位问题。所以,就先百度了下,发现没有相关文章,就只好自己动手来解决了。先看看不处理的效果图吧:
这效果,产品能忍,测试不能忍,测试能忍,我不能忍
解决:
出现问题不可怕,找出问题是关键。那么是什么导致的呢?其实就是拖拽的时候,拖拽了非顶层的item。那么,我的需求就变成了,禁止拖拽其他非顶层item,只能拖拽顶层item。有没有这样的方法?
先看看ItemTouchHelper.SimpleCallback 的几个方法:
/** * Called when ItemTouchHelper wants to move the dragged item from its old position to * the new position. * 巴拉巴拉一堆,说了在想拖拽交换位置的时候调用,一般在这里设置监听 */ public abstract boolean onMove(RecyclerView recyclerView, ViewHolder viewHolder, ViewHolder target); /** * Called when a ViewHolder is swiped by the user. * <p> * If you are returning relative directions ({@link #START} , {@link #END}) from the * {@link #getMovementFlags(RecyclerView, ViewHolder)} method, this method * 位置交换后调用。一般在这个时候对数据进行处理 */
public abstract void onSwiped(ViewHolder viewHolder, int direction);
/** * Called by ItemTouchHelper on RecyclerView's onDraw callback. * 又讲了一堆。简单说,就是拖拽过程子view的绘制监听。可以在这里对拖拽过程加上一些动画等。 */ public void onChildDraw(Canvas c, RecyclerView recyclerView, ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) { sUICallback.onDraw(c, recyclerView, viewHolder.itemView, dX, dY, actionState, isCurrentlyActive); }
那有没有禁止拖拽的方法呢?有的。
/** * Returns whether ItemTouchHelper should start a swipe operation if a pointer is swiped * over the View. */ public boolean isItemViewSwipeEnabled() { return true; }
只是重写方法后,返回false,那么所有item都不能拖拽了。这可和我的需求不符合。
问题来了。怎么办?
既然上面方法可以禁止所有item拖拽,说明,这个方法还是很有用的。找到这个方法在哪里被调用了。然后针对想要的效果处理一下,比如我想让顶层item可以滑动。那就在判断的时候,过滤掉这个判断或修改返回值不就可以了么?
找到位置了。可以看到,是在ItemTouchHelper 的checkSelectForSwipe()方法里做判断。位置找到了。在运行一遍程序,看下调用过程。发现,每次拖拽,都是先执行该方法,其他几个callback的方法才执行。其实,如果先执行其他方法的话,会更好办些,可以在重写的onChildDraw()方法里直接处理。可是不行呀。是先执行这个ItemTouchHelper里的方法。直接改android sdk的方法时不行的。那怎么办?
那就不用sdk提供的ItemTouchHelper类了。自己写一个一模一样的。然后,专门针对checkSelectForSwipe()方法处理。注意,ItemTouchHelper会用到一些sdk里的类,这些类默认不可包外引用,所以也要一并重写。
再看看如何处理checkSelectForSwipe方法吧
/** * Checks whether we should select a View for swiping. * 可以看到,比原来方法多出了个参数: RecyclerView , 是为了拿到最大个数。可根据自己的需求,去掉或者增加其他参数。 * 毕竟,这里是自己的类了。想怎么处理怎么处理 */ boolean checkSelectForSwipe(RecyclerView recyclerView,int action, MotionEvent motionEvent, int pointerIndex) { if (mRecyclerView.getScrollState() == RecyclerView.SCROLL_STATE_DRAGGING) { return false; } final RecyclerView.ViewHolder vh = findSwipedView(motionEvent); if (vh == null) { return false; } int position = vh.getPosition(); //拿到当前拖拽的item位置 int size = recyclerView.getAdapter().getItemCount()-1; //顶层item所在位置 if(position != size) return false; //如果当前拖拽item不是顶层item,则返回false,表示不swipe。 否则拖拽item开始随手势滑动。 if (mSelected != null || action != MotionEvent.ACTION_MOVE || mActionState == ACTION_STATE_DRAG || !mCallback.isItemViewSwipeEnabled()) { //将该部分校验下移,先拿到vh和recyclerview . return false; } final int movementFlags = mCallback.getAbsoluteMovementFlags(mRecyclerView, vh); final int swipeFlags = (movementFlags & ACTION_MODE_SWIPE_MASK) >> (DIRECTION_FLAG_COUNT * ACTION_STATE_SWIPE); if (swipeFlags == 0) { return false; } .......................................................................................省略拖拽滑动部分的代码,可以自己去看 return true; }
改完这些。在跑一下代码看看效果:
今天传图一直有问题。第一个图折腾了好久才传到github上。这个效果图上传上了。就是展示不出来。
项目demo地址:https://github.com/shoneworn/SwipeCard 喜欢就给个小star
如果你积分比较多,可以选择到此处下载:
https://download.csdn.net/download/shoneworn/10521930