代码如下:
var helper=ItemTouchHelper(object :ItemTouchHelper.Callback(){
//这里用来判断处理哪些情况
override fun getMovementFlags(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder): Int {
var position=viewHolder.adapterPosition
if(position==0){
return 0 //这里模拟,第一个位置不允许拖动
}
val dragFlags = ItemTouchHelper.UP or ItemTouchHelper.DOWN or ItemTouchHelper.START or ItemTouchHelper.END //这个是拖动的flag
val swipeFlags = ItemTouchHelper.UP or ItemTouchHelper.DOWN //这个是滑动的flag,比如滑动删除,这里说明下,如果是HORIZONTAL方向的layoutManager,那滑动删除只有上下,垂直方向的话是左右。
return makeMovementFlags(dragFlags,swipeFlags)
}
//拖拽的时候会不停的回掉这个方法,我们在这里做的就是交换对应的数据
override fun onMove(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder): Boolean {
var oldPositioon= viewHolder.adapterPosition
var newPosition=target.adapterPosition
if(newPosition==0){
return false
}
Collections.swap(wordsOriginal,oldPositioon,newPosition)
recyclerView.adapter.notifyItemMoved(oldPositioon,newPosition)
return true
}
//上边的onMove返回值为true的时候,会执行这个方法。其中x和y应该是当前移动的view的left和top的位置,
//也就是原始的left和top加上手指滑动的距离。数据的交换也可以写在这个方法里
override fun onMoved(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder?, fromPos: Int, target: RecyclerView.ViewHolder?, toPos: Int, x: Int, y: Int) {
super.onMoved(recyclerView, viewHolder, fromPos, target, toPos, x, y)
println("71=============moved==$fromPos->$toPos====$x/$y")
// Collections.swap(urls,fromPos,toPos)
// Collections.swap(books,fromPos,toPos)
// recyclerView.adapter.notifyItemMoved(fromPos,toPos)
}
//使用kotlin的时候得注意,这个viewHolder是可能为空的,
//滑动或者拖拽的时候都会走这里的
override fun onSelectedChanged(viewHolder: RecyclerView.ViewHolder?, actionState: Int) {
super.onSelectedChanged(viewHolder, actionState)
viewHolder?.run {
if (actionState != ItemTouchHelper.ACTION_STATE_IDLE) {
itemView.setBackgroundColor(Color.RED);
}
}
}
//滑动或者拖动结束以后执行
override fun clearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) {
super.clearView(recyclerView, viewHolder)
viewHolder.itemView.setBackgroundColor(0);
}
//这个就是滑动删除的时候用的,方向就是getMovementFlags()里返回的swipeFlags,看它支持哪个方向滑动,这里返回其中一个滑动方向
//当划出屏幕以后会回掉这里,我们要做的就是把数据删除掉。
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
var rect=Rect()
viewHolder.itemView.getLocalVisibleRect(rect)
println("==============on swiped direction=$direction ${rect}")
}
//这个就是滑动删除的时候的一个阈值,系统默认的是滑动child宽度一半的时候判定为删除 。参考下边的说明
public float getSwipeThreshold(ViewHolder viewHolder) {
return .5f;
}
//这个就是我们拖拽的时候移动多少距离就判定为可以交换了
public float getMoveThreshold(ViewHolder viewHolder) {
return .5f;
}
})
helper.attachToRecyclerView(rv_words)
swipThreshold相关
private int checkHorizontalSwipe(ViewHolder viewHolder, int flags) {
....省略
final float threshold = mRecyclerView.getWidth() * mCallback
.getSwipeThreshold(viewHolder);
if ((flags & dirFlag) != 0 && Math.abs(mDx) > threshold) {
return dirFlag;
}
}
return 0;
}
private int checkVerticalSwipe(ViewHolder viewHolder, int flags) {
...省略
final float threshold = mRecyclerView.getHeight() * mCallback
.getSwipeThreshold(viewHolder);
if ((flags & dirFlag) != 0 && Math.abs(mDy) > threshold) {
return dirFlag;
}
}
return 0;
}
moveThreshold相关
下边的方法是在OnItemTouchListener里的onTouchEvent方法里的MotionEvent.ACTION_MOVE事件处理的
void moveIfNecessary(ViewHolder viewHolder) {
if (mRecyclerView.isLayoutRequested()) {
return;
}
if (mActionState != ACTION_STATE_DRAG) {
return;
}
final float threshold = mCallback.getMoveThreshold(viewHolder);
final int x = (int) (mSelectedStartX + mDx);
final int y = (int) (mSelectedStartY + mDy);
if (Math.abs(y - viewHolder.itemView.getTop()) < viewHolder.itemView.getHeight() * threshold
&& Math.abs(x - viewHolder.itemView.getLeft())
< viewHolder.itemView.getWidth() * threshold) {
return;//这里如果移动的距离x方向和y方向距离都小于对应的宽高的一半的话那就return掉,不移动。
}
List swapTargets = findSwapTargets(viewHolder);
if (swapTargets.size() == 0) {
return;
}
// may swap.
ViewHolder target = mCallback.chooseDropTarget(viewHolder, swapTargets, x, y);
if (target == null) {
mSwapTargets.clear();
mDistances.clear();
return;
}
final int toPosition = target.getAdapterPosition();
final int fromPosition = viewHolder.getAdapterPosition();
if (mCallback.onMove(mRecyclerView, viewHolder, target)) {
// keep target visible,如果需要移动了,就回掉onMoved方法了
mCallback.onMoved(mRecyclerView, viewHolder, fromPosition,
target, toPosition, x, y);
}
}
这个我觉得注释的不错,可以参考,不过它最后介绍的那个禁止拖动选项的有点麻烦。可以不参考
https://www.cnblogs.com/wjtaigwh/p/6543354.html
现在简单看下源码,了解下大概逻辑
我们的Callback是靠ItemTouchHelper存活的。
首先attachToRecyclerView方法绑定RecyclerView,代码很简单,如果有旧的rv就先取消掉相关的绑定,然后看下setupCallbacks
public void attachToRecyclerView(@Nullable RecyclerView recyclerView) {
if (mRecyclerView == recyclerView) {
return; // nothing to do
}
if (mRecyclerView != null) {
destroyCallbacks();
}
mRecyclerView = recyclerView;
if (mRecyclerView != null) {
final Resources resources = recyclerView.getResources();
mSwipeEscapeVelocity = resources
.getDimension(R.dimen.item_touch_helper_swipe_escape_velocity);
mMaxSwipeVelocity = resources
.getDimension(R.dimen.item_touch_helper_swipe_escape_max_velocity);
setupCallbacks();
}
}
都是给RecyclerView配置的
private void setupCallbacks() {
ViewConfiguration vc = ViewConfiguration.get(mRecyclerView.getContext());
mSlop = vc.getScaledTouchSlop();
mRecyclerView.addItemDecoration(this);
mRecyclerView.addOnItemTouchListener(mOnItemTouchListener);
mRecyclerView.addOnChildAttachStateChangeListener(this);
initGestureDetector();
}
1callback方法里2个onDraw的调用逻辑
首先看下 mRecyclerView.addItemDecoration(this);
这句里itemDecoration相关,可以看到onDraw和onDrawOver里分别调用了callback里Ondraw和OndrawOver方法
@Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
float dx = 0, dy = 0;
if (mSelected != null) {
getSelectedDxDy(mTmpPosition);
dx = mTmpPosition[0];
dy = mTmpPosition[1];
}
mCallback.onDrawOver(c, parent, mSelected,
mRecoverAnimations, mActionState, dx, dy);
}
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
// we don't know if RV changed something so we should invalidate this index.
mOverdrawChildPosition = -1;
float dx = 0, dy = 0;
if (mSelected != null) {
getSelectedDxDy(mTmpPosition);
dx = mTmpPosition[0];
dy = mTmpPosition[1];
}
mCallback.onDraw(c, parent, mSelected,
mRecoverAnimations, mActionState, dx, dy);
}
看一下2个onChildDraw[Over]啥时候掉用的,
看完可以知道,我们如果要做啥处理,可以重写callback里如下方法做一些处理
override fun onChildDraw(c: Canvas?, recyclerView: RecyclerView?, viewHolder: RecyclerView.ViewHolder?, dX: Float, dY: Float, actionState: Int, isCurrentlyActive: Boolean) {
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive)
}
override fun onChildDrawOver(c: Canvas?, recyclerView: RecyclerView?, viewHolder: RecyclerView.ViewHolder?, dX: Float, dY: Float, actionState: Int, isCurrentlyActive: Boolean) {
super.onChildDrawOver(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive)
}
2 initGestureDetector();手势检测,这里就处理了下长按事件
private class ItemTouchHelperGestureListener extends GestureDetector.SimpleOnGestureListener {
ItemTouchHelperGestureListener() {
}
@Override
public boolean onDown(MotionEvent e) {
return true;
}
@Override
public void onLongPress(MotionEvent e) {
View child = findChildView(e);
if (child != null) {
ViewHolder vh = mRecyclerView.getChildViewHolder(child);
if (vh != null) {
if (!mCallback.hasDragFlag(mRecyclerView, vh)) {
return;
}
int pointerId = e.getPointerId(0);
// Long press is deferred.
// Check w/ active pointer id to avoid selecting after motion
// event is canceled.
if (pointerId == mActivePointerId) {
final int index = e.findPointerIndex(mActivePointerId);
final float x = e.getX(index);
final float y = e.getY(index);
mInitialTouchX = x;
mInitialTouchY = y;
mDx = mDy = 0f;
if (DEBUG) {
Log.d(TAG,
"onlong press: x:" + mInitialTouchX + ",y:" + mInitialTouchY);
}
if (mCallback.isLongPressDragEnabled()) {
select(vh, ACTION_STATE_DRAG);
}
}
}
}
}
}
可以看到最后的代码调用了select方法,里边又一堆判断,各种操作
void select(ViewHolder selected, int actionState) {
//省略N行代码
final ViewParent rvParent = mRecyclerView.getParent();
if (rvParent != null) {
rvParent.requestDisallowInterceptTouchEvent(mSelected != null);
}
if (!preventLayout) {
mRecyclerView.getLayoutManager().requestSimpleAnimationsInNextLayout();
}
mCallback.onSelectedChanged(mSelected, mActionState);
mRecyclerView.invalidate();
可以看到最后调用了callback的onSelectedChanged方法
3callback里的onMove onMoved
private final OnItemTouchListener mOnItemTouchListener = new OnItemTouchListener() {
@Override
public void onTouchEvent(RecyclerView recyclerView, MotionEvent event) {
mGestureDetector.onTouchEvent(event);
//省略。。
switch (action) {
case MotionEvent.ACTION_MOVE: {
// Find the index of the active pointer and fetch its position
if (activePointerIndex >= 0) {
updateDxDy(event, mSelectedFlags, activePointerIndex);
moveIfNecessary(viewHolder);
mRecyclerView.removeCallbacks(mScrollRunnable);
mScrollRunnable.run();
mRecyclerView.invalidate();
}
break;
}
case MotionEvent.ACTION_CANCEL:
if (mVelocityTracker != null) {
mVelocityTracker.clear();
}
// fall through
case MotionEvent.ACTION_UP:
select(null, ACTION_STATE_IDLE);
mActivePointerId = ACTIVE_POINTER_ID_NONE;
break;
case MotionEvent.ACTION_POINTER_UP: {
final int pointerIndex = event.getActionIndex();
final int pointerId = event.getPointerId(pointerIndex);
if (pointerId == mActivePointerId) {
// This was our active pointer going up. Choose a new
// active pointer and adjust accordingly.
final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
mActivePointerId = event.getPointerId(newPointerIndex);
updateDxDy(event, mSelectedFlags, pointerIndex);
}
break;
}
}
}
}
简单说下这个ACTION_UP事件里select(null, ACTION_STATE_IDLE);
上边说过这个方法,最后会走这里mCallback.onSelectedChanged(mSelected, mActionState);
可以看到,手抬起来的时候,这个viewHolder传的是个空的
来分析下ACTION_MOVE事件里调用了moveIfNecessary(viewHolder);
代码如下
void moveIfNecessary(ViewHolder viewHolder) {
if (mRecyclerView.isLayoutRequested()) {
return;
}
if (mActionState != ACTION_STATE_DRAG) {
return;
}
final float threshold = mCallback.getMoveThreshold(viewHolder);
final int x = (int) (mSelectedStartX + mDx);
final int y = (int) (mSelectedStartY + mDy);
if (Math.abs(y - viewHolder.itemView.getTop()) < viewHolder.itemView.getHeight() * threshold
&& Math.abs(x - viewHolder.itemView.getLeft())
< viewHolder.itemView.getWidth() * threshold) {
return;//这里根据设置的threshold,判断移动的距离和itemView大小比较,看是否可以移动。
}
List swapTargets = findSwapTargets(viewHolder);
if (swapTargets.size() == 0) {
return;
}
// may swap.
ViewHolder target = mCallback.chooseDropTarget(viewHolder, swapTargets, x, y);
if (target == null) {
mSwapTargets.clear();
mDistances.clear();
return;
}
final int toPosition = target.getAdapterPosition();
final int fromPosition = viewHolder.getAdapterPosition();
if (mCallback.onMove(mRecyclerView, viewHolder, target)) {//这个方法返回true,才会走下边的方法
// keep target visible
mCallback.onMoved(mRecyclerView, viewHolder, fromPosition,
target, toPosition, x, y);
}
}
简单分析下逻辑就是,移动的时候判断下移动的距离是否满足我们设置的阈值条件,默认threshold是0.5也就是item宽高的一半。
如果不满足就return啥也不干,如果满足,就会先调用onMove方法,看是否可以移动【返回true】,可以的话就会调用onMoved方法,如果用不到x,y的值,那么交换数据的操作写在这两个方法里都是可以的。距离参看开头的代码
3clearView()
最后一半action_up的时候会调用这个方法,我们可以在这个方法里对itemView进行还原操作,比如我们在onSelectedChanged的时候修改了itemView的状态,就可以在这个方法里还原了。