ItemTouchHelper的使用
ItemTouchHelper可以解释为:Item点击的帮助类,它可以判断帮助我们快速的实现侧滑Swiped、长按拖动Move的效果。如下图的效果,用ItemTouchHelper就可以很简单的实现。
1.实现ItemTouchHelper.CallBack接口这个接口我们可以决定是否可以侧滑和拖动,以及方向,并知道侧滑、拖动的执行时的方法回调。
class DefaultItemTouchHelper(private var itemTouchStatus: ItemTouchStatus) :
ItemTouchHelper.Callback() {
/**
* dragFlags:拖动支持的方向
* swipeFlags: 滑动支持的方向
*/
override fun getMovementFlags(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder
): Int {
val dragFlags = ItemTouchHelper.UP or ItemTouchHelper.DOWN
val swipeFlags = ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT
// 通过makeMovementFlags确定拖动、侧滑的方向
return makeMovementFlags(dragFlags, swipeFlags)
}
// 长按拖动
override fun onMove(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder,
target: RecyclerView.ViewHolder
): Boolean {
return itemTouchStatus.onItemMove(viewHolder.adapterPosition, target.adapterPosition)
}
// 侧滑
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
itemTouchStatus.onItemRemove(viewHolder.adapterPosition)
}
}
在拖动、侧滑对Adapter进行数据更新
public class RvAdapter extends RecyclerView.Adapter implements ItemTouchStatus {
// ....省略Adapter的View创建和数据绑定
// 侧滑删除
@Override
public void onItemRemove(int position) {
data.remove(position);
notifyItemRemoved(position);
}
//长按拖动数据交换
@Override
public boolean onItemMove(int position, int targetPosition) {
Collections.swap(data, position, targetPosition);
notifyItemMoved(position, targetPosition);return true;
}
}
[Activity]将Rv绑到ItemTouchHelper中
val itemTouchHelper = ItemTouchHelper(DefaultItemTouchHelper(adapter!!))
itemTouchHelper.attachToRecyclerView(recyclerView)
实现此功能可以概括为三步:
- 实现ItemTouchHelper.CallBack接口
- 在ItemTouchHelper实例化时将CallBack传给实例
- 通过ItemTouchHelper. attachToRecyclerView将RecyclerView绑定到ItemTouchHelper中。
ItemTouchHelper源码学习
带着问题看源码:
- 为什么RecyclerView的Item在侧滑的时候会调用ItemTouchHelper.CallBack.onSwiped方法?
- 为什么RecyclerView的Item在长按拖动的时候会调用ItemTouchHelper.CallBack.onMoved方法?
- 为什么只通过一行代码attachToRecyclerView绑定RecyclerView后就可以操作RecyclerView的监听事件?
ItemTouchHelper的绑定
先看看ItemTouchHelper的构造函数和attachToRecyclerView [ItemTouchHelper#attachToRecyclerView]
// 接口实例
public ItemTouchHelper(@NonNull Callback callback) {
mCallback = callback;
}
public void attachToRecyclerView(@Nullable RecyclerView recyclerView) {
if (mRecyclerView == recyclerView) {
return; // nothing to do
}
// 有旧的RecyclerView,则取消原有监听
if (mRecyclerView != null) {
destroyCallbacks();
}
mRecyclerView = recyclerView;
if (recyclerView != 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();
}
}
private void setupCallbacks() {
ViewConfiguration vc = ViewConfiguration.get(mRecyclerView.getContext());
mSlop = vc.getScaledTouchSlop();
mRecyclerView.addItemDecoration(this);
// 这个监听很重要,可以截取RecylerView的手势事件
mRecyclerView.addOnItemTouchListener(mOnItemTouchListener);
mRecyclerView.addOnChildAttachStateChangeListener(this);
startGestureDetection();
}
我们在使用ItemTouchHelper时,也就是用到这个类中的两个方法, 一构造函数必须传一个CallBack接口的实例。二是attachToRecyclerView方法会给传来的RecyclerView设置监听,其中设置了OnItemTouchListener
监听可以拦截RecyclerView的手势事件,从而使ItemTouchHelper可以拦截RecylerView的事件进行处理。startGestureDetection()
方法创建了GestureDetection用来识别长按手势。
RecyclerView的事件托管
onInterceptTouchEvent[RecyclerView.onInterceptTouchEvent]
// 是否拦截事件
@Override
public boolean onInterceptTouchEvent(MotionEvent e) {
if (mLayoutSuppressed) {
// When layout is suppressed, RV does not intercept the motion event.
// A child view e.g. a button may still get the click.
return false;
}
// 当前处理的OnItemTouchListener,重新设置为null
mInterceptingOnItemTouchListener = null;
// 先检查是否设置了OnItemTouchListener,那就事件先交给OnItemTouchListener处理
if (findInterceptingOnItemTouchListener(e)) {
// 取消滑动
cancelScroll();
return true;
}
// RecylerView自身处理拦截事件
//....
}
private boolean findInterceptingOnItemTouchListener(MotionEvent e) {
int action = e.getAction();
// 遍历onItemTouchListener集合
final int listenerCount = mOnItemTouchListeners.size();
for (int i = 0; i final OnItemTouchListener listener = mOnItemTouchListeners.get(i);
//执行监听实例的onInterceptTouchEvent方法,如果返回ture和手势不是取消事件,则设置当前处理的OnItemTouchListener和返回Ture拦截事件。
if (listener.onInterceptTouchEvent(this, e) && action != MotionEvent.ACTION_CANCEL) {
mInterceptingOnItemTouchListener = listener;
return true;
}
}
return false;
}
RecyclerView在执行拦截事件onInterceptTouchEvent
会先遍历OnItemTouchListener集合,查找在OnItemTouchListener.onInterceptTouchEvent
中返回true的OnItemTouchListener,并把它设置为当前处理的onItemToucheListener
,返回Ture拦截事件且不执行RecyclerView本身处理的onInterceptTouchEvent代码。
onTouchEvent[RecyclerView.onToucheEvent]
@Override
public boolean onTouchEvent(MotionEvent e) {
if (mLayoutSuppressed || mIgnoreMotionEventTillDown) {
return false;
}
// 如果有当前处理的OnItemTouchListener
if (dispatchToOnItemTouchListeners(e)) {
cancelScroll();
return true;
}
//....RecyclerView自身对onTouchEvent方法的处理
}
private boolean dispatchToOnItemTouchListeners(MotionEvent e) {
if (mInterceptingOnItemTouchListener == null) {
// 手势开始时,没有要处理的OnItemTouchListener则返回false
if (e.getAction() == MotionEvent.ACTION_DOWN) {
return false;
}
// 手势在其他事件时,在去询问一下是否拦截,防止中途变更未及时更新
return findInterceptingOnItemTouchListener(e);
} else {
// 调用OnItemTouchListener的.onTouchEvent()方法
mInterceptingOnItemTouchListener.onTouchEvent(this, e);
final int action = e.getAction();
// 一个手势结束后,当前的OnItemTouchListener置null
if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
mInterceptingOnItemTouchListener = null;
}
return true;
}
}
// 遍历OnItemTouchListener集合的onInterceptTouchEvent是否拦截
private boolean findInterceptingOnItemTouchListener(MotionEvent e) {
int action = e.getAction();
// 遍历onItemTouchListener集合
final int listenerCount = mOnItemTouchListeners.size();
for (int i = 0; i final OnItemTouchListener listener = mOnItemTouchListeners.get(i);
//执行监听实例的onInterceptTouchEvent方法,如果返回ture和手势不是取消事件,则设置当前处理的OnItemTouchListener和返回Ture拦截事件。
if (listener.onInterceptTouchEvent(this, e) && action != MotionEvent.ACTION_CANCEL) {
mInterceptingOnItemTouchListener = listener;
return true;
}
}
return false;
}
在RecyclerView.onTouchEvent
也会先查询是否有当前处理的onItemTouchListener,如果有onItemTouchListener,则调用onItemTouchListener.onTouchEvent,并返回True
。如果没有onItemTouchListener,则先判断手势是否为DOWN
刚开始则返回false,不是则调用findInterceptingOnItemTouchListener继续监听onItemTouchListener. onInterceptTouchEvent
是否有更新。
requestDisallowInterceptTouchEvent[RecyclerView.requestDisallowInterceptTouchEvent]
@Override
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
// 先遍历OnItemTouchListener集合,调用其onRequestDisallowInterceptTouchEvent是否拦截
final int listenerCount = mOnItemTouchListeners.size();
for (int i = 0; i final OnItemTouchListener listener = mOnItemTouchListeners.get(i);
listener.onRequestDisallowInterceptTouchEvent(disallowIntercept);
}
super.requestDisallowInterceptTouchEvent(disallowIntercept);
}
requestDisallowInterceptTouchEvent
可以向父布局表明是否需求拦截事件。RecylerView.requestDisallowInterceptTouchEvent
则先让OnItemTouchListener先决定是否拦截。
RecyclerView重写的三个拦截方法都让OnItemTouchListener托管了,所以我们要控制RecyclerView的拦截以及手势事件只要设置OnItemTouchListener接口监听就可以了。
public interface OnItemTouchListener {
boolean onInterceptTouchEvent(@NonNull RecyclerView rv, @NonNull MotionEvent e);
void onTouchEvent(@NonNull RecyclerView rv, @NonNull MotionEvent e);
void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept);
}
public static class SimpleOnItemTouchListener implements RecyclerView.OnItemTouchListener {
@Override
public boolean onInterceptTouchEvent(@NonNull RecyclerView rv, @NonNull MotionEvent e) {
return false;
}
@Override
public void onTouchEvent(@NonNull RecyclerView rv, @NonNull MotionEvent e) {
}
@Override
public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
}
}
不过如果你要控制onTouchEvent
事件,要先让onInterceptTouchEvent方法返回true
。所以ItemTouchHelper
类就是同时给绑定的RecyclerView
设置OnItemTouchListener
监听来控制RecyclerView的手势事件。
接下来,我们看看ItemTouchHelper
是怎样实例化OnItemTouchListener
的三个接口方法。
ItemTouchHelper的事件拦截
[ItemTouchHelper.OnItemTouchListener]
private final OnItemTouchListener mOnItemTouchListener = new OnItemTouchListener() {
@Override
public boolean onInterceptTouchEvent(@NonNull RecyclerView recyclerView,
@NonNull MotionEvent event) {
// GestureDetector进行长按监听
mGestureDetector.onTouchEvent(event);
if (DEBUG) {
Log.d(TAG, "intercept: x:" + event.getX() + ",y:" + event.getY() + ", " + event);
}
final int action = event.getActionMasked();
// 手势开始的时候做一个变量的初始化
if (action == MotionEvent.ACTION_DOWN) {
mActivePointerId = event.getPointerId(0);
mInitialTouchX = event.getX();
mInitialTouchY = event.getY();
obtainVelocityTracker();
//mSelected表示当前选中的ViewHolder
if (mSelected == null) {
final RecoverAnimation animation = findAnimation(event);
if (animation != null) {
mInitialTouchX -= animation.mX;
mInitialTouchY -= animation.mY;
endRecoverAnimation(animation.mViewHolder, true);
if (mPendingCleanup.remove(animation.mViewHolder.itemView)) {
mCallback.clearView(mRecyclerView, animation.mViewHolder);
}
// 设置mSelected的ViewHolder
select(animation.mViewHolder, animation.mActionState);
updateDxDy(event, mSelectedFlags, 0);
}
}
} else if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
// 当手势离开或取消时,更新PointerId和设置mSelected为null
mActivePointerId = ACTIVE_POINTER_ID_NONE;
select(null, ACTION_STATE_IDLE);
} else if (mActivePointerId != ACTIVE_POINTER_ID_NONE) { // 手势不是离开或取消或按下时
// 按下位置
final int index = event.findPointerIndex(mActivePointerId);
if (DEBUG) {
Log.d(TAG, "pointer index " + index);
}
// 检查是否需要侧滑,并通过select()方法设置mSelected
if (index >= 0) {
checkSelectForSwipe(action, event, index);
}
}
// 计算加速度
if (mVelocityTracker != null) {
mVelocityTracker.addMovement(event);
}
// 是否拦截决定与是否有选中的ViewHolder
return mSelected != null;
}
@Override
public void onTouchEvent(@NonNull RecyclerView recyclerView, @NonNull MotionEvent event) {
// 监听长按事件
mGestureDetector.onTouchEvent(event);
if (DEBUG) {
Log.d(TAG,
"on touch: x:" + mInitialTouchX + ",y:" + mInitialTouchY + ", :" + event);
}
// 计算加速度
if (mVelocityTracker != null) {
mVelocityTracker.addMovement(event);
}
//手势已经UP或CANCEL
if (mActivePointerId == ACTIVE_POINTER_ID_NONE) {
return;
}
// 检查是否需要侧滑,并通过select()方法设置mSelected
final int action = event.getActionMasked();
final int activePointerIndex = event.findPointerIndex(mActivePointerId);
if (activePointerIndex >= 0) {
checkSelectForSwipe(action, event, activePointerIndex);
}
ViewHolder viewHolder = mSelected;
if (viewHolder == null) {
return;
}
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:
// 设置mSelected为Null
select(null, ACTION_STATE_IDLE);
mActivePointerId = ACTIVE_POINTER_ID_NONE;
break;
case MotionEvent.ACTION_POINTER_UP: {
// 多指中的一个抬起,更新相关变量id
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;
}
}
}
@Override
public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
if (!disallowIntercept) {
return;
}
// 重新设置mSelected为Null
select(null, ACTION_STATE_IDLE);
}
};
这里的mSelected
代表选中的Item的ViewHolder
,如果为null
表示没有选中的Item。
onInterceptTouchEvent方法在DOWN
时更新一些变量,并在mSelected == null
时根据event
的点击坐标找到对应Item的动画
,如果动画不为null,则调用select
方法设置mSelected。在CANCEL或UP
时设置mSelected为null,并将手势id设置为ACTIVE_POINTER_ID_NONE
。最后在手势id不等于ACTIVE_POINTER_ID_NONE
时调用checkSelectForSwipe()
方法查看是否需要侧滑。最后根据选中的ViewHolder的mSelected
变量是否为null来决定是否拦截。
onTouchEvent首先调用checkSelectForSwipe
方法是否需要侧滑,然后在MOVE
时调用moveIfNecessary()
方法是否需要长按拖动。
onRequestDisallowInterceptTouchEvent方法则是在表明要拦截事件时,将mSelected设置为null。
**总结:**在onInterceptTouchEvent
方法中在非CANCEL和UP
手势时,调用checkSelectForSwipe
查询是否为侧滑。在onTouchEvent
有执行时就表示mSelected != null
,先是调用checkSelectForSwipe
方法查询是否为侧滑,然后在MOVE
手势时调用moveIfNecessary
方法查询是否为长按拖动。
接下来就看看checkSelectForSwipe和moveIfNecessary
的两个方法。
侧滑和长按拖动的调用时机
SWIPE侧滑触发ItemTouchHelper在onInterceptTouchEvent和onTouchEvent在事件开始时调用checkSelectForSwipe()检查SWIP。
void checkSelectForSwipe(int action, MotionEvent motionEvent, int pointerIndex) {
// 已选中ViewHolder、手势不是MOVE、状态不是DRAG、接口定义不可以侧滑
if (mSelected != null || action != MotionEvent.ACTION_MOVE
|| mActionState == ACTION_STATE_DRAG || !mCallback.isItemViewSwipeEnab
return;
}
// recyclerView在滚动
if (mRecyclerView.getScrollState() == RecyclerView.SCROLL_STATE_DRAGGING) {
return;
}
// 根据手势位置找到可侧滑的ViewHolder,手势侧滑的方向不可以与RecyclerView可滑动的方向相同
final ViewHolder vh = findSwipedView(motionEvent);
if (vh == null) {
return;
}
// 询问接口返回可以侧滑和拖动的手势方向
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;
}
// 最后点位置
final float x = motionEvent.getX(pointerIndex);
final float y = motionEvent.getY(pointerIndex);
// x,y方向滑动的距离
final float dx = x - mInitialTouchX;
final float dy = y - mInitialTouchY;
final float absDx = Math.abs(dx);
final float absDy = Math.abs(dy);
if (absDx return;
}
if (absDx > absDy) {
// 手指向左滑
if (dx return;
}
// 手指向右滑
if (dx > 0 && (swipeFlags & RIGHT) == 0) {
return;
}
} else {
// 手指向上滑
if (dy return;
}
// 手指向下滑
if (dy > 0 && (swipeFlags & DOWN) == 0) {
return;
}
}
mDx = mDy = 0f;
mActivePointerId = motionEvent.getPointerId(0);
//设置mSelect,并把状态设置为SWIPE
select(vh, ACTION_STATE_SWIPE);
}
checkSelectForSwipe方法就做两件事:1.mSelected为null或MOVE时才往下进行。2.检查手势的滑动跟RecyclerView的滑动方向
和CallBack定义的侧滑方向
是否有冲突,都没有则调用select方法:设置mSelected,并且为SWIPE侧滑状态。
DRAG长按拖动触发ItemTouchEvent通过GestureDetector实例检测MotionEvent的长按操作,从而触发拖动操作。
private class ItemTouchHelperGestureListener extends GestureDetector.SimpleOnGestureListener {
private boolean mShouldReactToLongPress = true;
ItemTouchHelperGestureListener() {
}
void doNotReactToLongPress() {
mShouldReactToLongPress = false;
}
@Override
public boolean onDown(MotionEvent e) {
return true;
}
@Override
public void onLongPress(MotionEvent e) {
if (!mShouldReactToLongPress) {
return;
}
// 手势坐标对应的View
View child = findChildView(e);
if (child != null) {
// View的ViewHolder
ViewHolder vh = mRecyclerView.getChildViewHolder(child);
if (vh != null) {
// 检查CallBack是否有设置Drag方向
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);
}
// 接口是否支持Drag,默认支持
if (mCallback.isLongPressDragEnabled()) {
//设置mSelected,并且状态为DRAG
select(vh, ACTION_STATE_DRAG);
}
}
}
}
}
}
在onLongPress
长按回调方法中,先通过手势坐标找到ViewHolder,然后在检查CallBack是否设置了Drag方向
和是否能支持Drag
。
通过以上代码,我们知道了checkSelectForSwipe
和GestureDetector. onLongPress
两个方法分别确定了侧滑SWIPE和长按拖动DRAG的触发时机。但这只是开胃菜,接下来看看select
方法是怎样实现侧滑和长按拖动
侧滑和长按拖动的实现
在上面的分析得知,在侧滑和拖动触发的时候等会调用一个select(ViewHolder selected, int actionState)
方法,我们看看这个方法究竟做了什么?
void select(@Nullable ViewHolder selected, int actionState) {
if (selected == mSelected && actionState == mActionState) {
return;
}
mDragScrollStartTimeInMs = Long.MIN_VALUE;
final int prevActionState = mActionState;
// 新传来的ViewHolder防止重复动画
endRecoverAnimation(selected, true);
mActionState = actionState; // 赋值状态
// 如果是拖动,在<21的版本要设置RecyclerView的ChildDrawingOrderCallback监听
if (actionState == ACTION_STATE_DRAG) {
if (selected == null) {
throw new IllegalArgumentException("Must pass a ViewHolder when dragging");
}
mOverdrawChild = selected.itemView; // 在上面绘制的View
addChildDrawingOrderCallback(); // 设置
}
int actionStateMask = (1 < - 1;
boolean preventLayout = false;
// 对于原先有ViewHolder的情况,则要对之前的ViewHolder进行结束处理。
if (mSelected != null) {
final ViewHolder prevSelected = mSelected; // 原有之前的ViewHolder
// ItemView还吸附在RecyclerView
if (prevSelected.itemView.getParent() != null) {
// 如果之前是拖动则为0,如果是之前状态是侧滑,则计算加速度是否大于阀值,大于则返回需要滑动的距离
final int swipeDir = prevActionState == ACTION_STATE_DRAG ? 0
: swipeIfNecessary(prevSelected);
releaseVelocityTracker(); // 释放VelocityTracker
// find where we should animate to
final float targetTranslateX, targetTranslateY;
int animationType;
// 根据加速度的方向,将ItemView滑到边界。
switch (swipeDir) {
case LEFT:
case RIGHT:
case START:
case END:
targetTranslateY = 0;
targetTranslateX = Math.signum(mDx) * mRecyclerView.getWidth();
break;
case UP:
case DOWN:
targetTranslateX = 0;
targetTranslateY = Math.signum(mDy) * mRecyclerView.getHeight();
break;
default:
targetTranslateX = 0;
targetTranslateY = 0;
}
if (prevActionState == ACTION_STATE_DRAG) {
animationType = ANIMATION_TYPE_DRAG;
} else if (swipeDir > 0) {
animationType = ANIMATION_TYPE_SWIPE_SUCCESS; //滑动成功
} else {
animationType = ANIMATION_TYPE_SWIPE_CANCEL; //滑动取消
}
// 调用动画结束之前ViewHolder的操作
getSelectedDxDy(mTmpPosition);
final float currentTranslateX = mTmpPosition[0];
final float currentTranslateY = mTmpPosition[1];
final RecoverAnimation rv = new RecoverAnimation(prevSelected, animationType,
prevActionState, currentTranslateX, currentTranslateY,
targetTranslateX, targetTranslateY) {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
if (this.mOverridden) {
return;
}
if (swipeDir <= 0) {
// this is a drag or failed swipe. recover immediately
mCallback.clearView(mRecyclerView, prevSelected);
// full cleanup will happen on onDrawOver
} else {
// wait until remove animation is complete.
mPendingCleanup.add(prevSelected.itemView);
mIsPendingCleanup = true;
if (swipeDir > 0) {
// 动画结束,调用CallBack.onSwiped()
postDispatchSwipe(this, swipeDir);
}
}
// removed from the list after it is drawn for the last time
if (mOverdrawChild == prevSelected.itemView) {
removeChildDrawingOrderCallbackIfNecessary(prevSelected.itemView);
}
}
};
final long duration = mCallback.getAnimationDuration(mRecyclerView, animationType,
targetTranslateX - currentTranslateX, targetTranslateY - currentTranslateY);
rv.setDuration(duration);
mRecoverAnimations.add(rv);
rv.start(); // 开始动画
preventLayout = true;
} else {
// ItemView已经脱离了ReccyclerView,则将监听回收
removeChildDrawingOrderCallbackIfNecessary(prevSelected.itemView);
mCallback.clearView(mRecyclerView, prevSelected);
}
// mSelected则为null
mSelected = null;
}
// 设置新的ViewHolder
if (selected != null) {
// 赋值初始变量
mSelectedFlags =
(mCallback.getAbsoluteMovementFlags(mRecyclerView, selected) & actionStateMask)
>> (mActionState * DIRECTION_FLAG_COUNT);
mSelectedStartX = selected.itemView.getLeft();
mSelectedStartY = selected.itemView.getTop();
mSelected = selected;
若是触发DRAG,则给用户一个触觉反馈
if (actionState == ACTION_STATE_DRAG) {
mSelected.itemView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
}
}
// 根据ViewHolder是否为null设置requestDisallowInterceptTouchEvent
final ViewParent rvParent = mRecyclerView.getParent();
if (rvParent != null) {
rvParent.requestDisallowInterceptTouchEvent(mSelected != null);
}
// 之前没有Viewholder, 使RecyclerView在下一次布局时运行SimpleAnimation
if (!preventLayout) {
mRecyclerView.getLayoutManager().requestSimpleAnimationsInNextLayout();
}
// 调用接口onSelectedChanged和重新绘制
mCallback.onSelectedChanged(mSelected, mActionState);
mRecyclerView.invalidate();
}
通过把代码分析一遍发现,就是先把原先的ViewHolder如果侧滑的加速度大于设置的阀值就让它滚出界面,并进行一些现有已选ViewHolder变量赋值操作,并调用 invalidate
进行重绘,看来这里也没有真正实现侧滑和拖动效果。
ItemTouchHelper是实现了RecyclerView.ItemDecoration
,看看它的实现。
public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
RecyclerView.State state) {
// 不设置边距
outRect.setEmpty();
}
@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];
}
// 由Callback执行
mCallback.onDraw(c, parent, mSelected,
mRecoverAnimations, mActionState, dx, dy);
}
@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];
}
// 由Callback执行
mCallback.onDrawOver(c, parent, mSelected,
mRecoverAnimations, mActionState, dx, dy);
}
通过attachToRecyclerView
方法时,ItemTouchHelper就作为ItemDecoration绑定到RecyclerView的ItemDecoration集合中。ItemTouchHelper并没有设置分割线的大小。onDraw、onDrawOver的具体实现也交给了Callback执行。
public abstract static class Callback {
void onDraw(Canvas c, RecyclerView parent, ViewHolder selected,
List recoverAnimationList,
int actionState, float dX, float dY) {
final int recoverAnimSize = recoverAnimationList.size();for (int i = 0; i final ItemTouchHelper.RecoverAnimation anim = recoverAnimationList.get(i);
anim.update();
final int count = c.save();
onChildDraw(c, parent, anim.mViewHolder, anim.mX, anim.mY, anim.mActionState,false);
c.restoreToCount(count);
}if (selected != null) {
final int count = c.save();
onChildDraw(c, parent, selected, dX, dY, actionState, true);
c.restoreToCount(count);
}
}
void onDrawOver(Canvas c, RecyclerView parent, ViewHolder selected,
List recoverAnimationList,
int actionState, float dX, float dY) {
final int recoverAnimSize = recoverAnimationList.size();for (int i = 0; i final ItemTouchHelper.RecoverAnimation anim = recoverAnimationList.get(i);
final int count = c.save();
onChildDrawOver(c, parent, anim.mViewHolder, anim.mX, anim.mY, anim.mActionState,false);
c.restoreToCount(count);
}if (selected != null) {
final int count = c.save();
onChildDrawOver(c, parent, selected, dX, dY, actionState, true);
c.restoreToCount(count);
}
boolean hasRunningAnimation = false;for (int i = recoverAnimSize - 1; i >= 0; i--) {
final RecoverAnimation anim = recoverAnimationList.get(i);if (anim.mEnded && !anim.mIsPendingCleanup) {
recoverAnimationList.remove(i);
} else if (!anim.mEnded) {
hasRunningAnimation = true;
}
}if (hasRunningAnimation) {
parent.invalidate();
}
}
}
public void onChildDraw(@NonNull Canvas c, @NonNull RecyclerView recyclerView,
@NonNull ViewHolder viewHolder,float dX, float dY, int actionState, boolean isCurrentlyActive) {
ItemTouchUIUtilImpl.INSTANCE.onDraw(c, recyclerView, viewHolder.itemView, dX, dY,
actionState, isCurrentlyActive);
}
public void onChildDrawOver(@NonNull Canvas c, @NonNull RecyclerView recyclerView,
ViewHolder viewHolder,float dX, float dY, int actionState, boolean isCurrentlyActive) {
ItemTouchUIUtilImpl.INSTANCE.onDrawOver(c, recyclerView, viewHolder.itemView, dX, dY,
actionState, isCurrentlyActive);
}
class ItemTouchUIUtilImpl implements ItemTouchUIUtil {
@Override
public void onDraw(Canvas c, RecyclerView recyclerView, View view, float dX, float dY,
int actionState, boolean isCurrentlyActive) {
if (Build.VERSION.SDK_INT >= 21) {
if (isCurrentlyActive) {
Object originalElevation = view.getTag(R.id.item_touch_helper_previous_elevation);
if (originalElevation == null) {
originalElevation = ViewCompat.getElevation(view);
float newElevation = 1f + findMaxElevation(recyclerView, view);
ViewCompat.setElevation(view, newElevation);
view.setTag(R.id.item_touch_helper_previous_elevation, originalElevation);
}
}
}
view.setTranslationX(dX);
view.setTranslationY(dY);
}
@Override
public void onDrawOver(Canvas c, RecyclerView recyclerView, View view, float dX, float dY,
int actionState, boolean isCurrentlyActive) {
}
}
从最终的方法知道,是通过不断的setTranslationX
,setTranslationY
和移动或拖动ItemView。
什么时候调用CallBack的onSwiped、onMove方法
onSwiped在select方法结束动画之后调用:
void select(@Nullable ViewHolder selected, int actionState) {
if (mSelected != null) {
final RecoverAnimation rv = new RecoverAnimation(prevSelected, animationType,
prevActionState, currentTranslateX, currentTranslateY,
targetTranslateX, targetTranslateY) {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
if (this.mOverridden) {
return;
}
if (swipeDir <= 0) {
mCallback.clearView(mRecyclerView, prevSelected);
} else {
if (swipeDir > 0) {
// 调用CallBack.onSwiped()
postDispatchSwipe(this, swipeDir);
}
}
}
};
}
}
void postDispatchSwipe(final RecoverAnimation anim, final int swipeDir) {
// wait until animations are complete.
mRecyclerView.post(new Runnable() {
@Override
public void run() {
if (mRecyclerView != null && mRecyclerView.isAttachedToWindow()
&& !anim.mOverridden
&& anim.mViewHolder.getAbsoluteAdapterPosition()
!= RecyclerView.NO_POSITION) {
final RecyclerView.ItemAnimator animator = mRecyclerView.getItemAnimator();
// if animator is running or we have other active recover animations, we try
// not to call onSwiped because DefaultItemAnimator is not good at merging
// animations. Instead, we wait and batch.
if ((animator == null || !animator.isRunning(null))
&& !hasRunningRecoverAnim()) {
mCallback.onSwiped(anim.mViewHolder, swipeDir);
} else {
mRecyclerView.post(this);
}
}
}
});
}
onMove在moveIfNecessary方法中调用:
*/
@SuppressWarnings("WeakerAccess") /* synthetic access */
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()) && Math.abs(x - viewHolder.itemView.getLeft())
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.getAbsoluteAdapterPosition();
final int fromPosition = viewHolder.getAbsoluteAdapterPosition();if (mCallback.onMove(mRecyclerView, viewHolder, target)) {
// keep target visible
mCallback.onMoved(mRecyclerView, viewHolder, fromPosition,
target, toPosition, x, y);
}
}