在写上一篇vlayout源码解析第一篇的时候,我发现随着源码的深入,这个框架对RecycleView的运用已达到如火纯青的地步,也就是说写这个框架的哥们对RecyclerView源码已经研究的相当透彻,那么为了更好的理解这个框架,就要先来研究一下RecyclerView源码。今天的主题是ItemTouchHelper源码详解,怎么最快的实现侧滑删除的效果,先看效果图:
这样的效果要是用以前的自定义控件的方式的话起码得上千行代码,而用ItemTouchHelper的话,少量的代码就可以搞定,先看一下怎么ItemTouchHelper怎么和RecycleView绑定的。
ItemTouchHelper.Callback callback = new MyItemTouchHelperCallback(
(ItemTouchMoveListener) adapter);
ItemTouchHelper itemTouchHelper = new ItemTouchHelper(callback);
itemTouchHelper.attachToRecyclerView(messagemain_lsitview);
三行代码实现RecyclerView绑定ItemTouchHelper,如果想定义自己的效果只需要重写
ItemTouchHelper.Callback这个类即可,但是前提你得知道需要覆写那些方法,这些方法到底是干什么的。虽然有文档可看,但是文档的意思模棱两可,很难体会它想表达的意思,只能说有时候文档确实很坑。那么唯一的办法就是看源码了,好继续进入itemTouchHelper.attachToRecyclerView(messagemain_lsitview)这个方法public void attachToRecyclerView(RecyclerView recyclerView) {
//已经绑定了同一个RecyclerView就返回
if (mRecyclerView == recyclerView) {
return; // nothing to do
}
//先释放解绑以前的
if (mRecyclerView != null) {
destroyCallbacks();
}
mRecyclerView = recyclerView;
if (mRecyclerView != null) {
//RecycleView开始绑定ItemTouchHelper
setupCallbacks();
}
}
/*
* 绑定ItemTouchHelp
*/
private void setupCallbacks() {
ViewConfiguration vc = ViewConfiguration
.get(mRecyclerView.getContext());
//得到认为滑动的最小的距离
mSlop = vc.getScaledTouchSlop();
//添加分割线画法,显然这里不是用来画分割线的
mRecyclerView.addItemDecoration(this);
//添加触发事件的监听函数,RecycleView通过mOnItemTouchListener实现和itemtouch的交互
mRecyclerView.addOnItemTouchListener(mOnItemTouchListener);
//子View被attach或dettach时通知回调
mRecyclerView.addOnChildAttachStateChangeListener(this);
//初始化手势
initGestureDetector();
}
这两个方法就是ItemTouchHelper内部绑定RecyclerView的方法,这里有一个奇怪的方法
mRecyclerView.addItemDecoration(this)添加分割线,这里为啥要添加分割线的监听回调
public class ItemTouchHelper extends RecyclerView.ItemDecoration
奥,原来是
ItemTouchHelper 继承自ItemDecoration,我们知道RecyclerView不像ListView那样直接设置俩个属性就会出现分割线,想要在RecyclerView上实现分割线的话,那么必须实现ItemDecoration这个类让RecycleView去根据这个类去画这个分割线,当然不止是分割线,你也可以画一些其他的东西,只要你有创意。public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e);
/**
* Process a touch event as part of a gesture that was claimed by
* returning true from a previous call to {@link #onInterceptTouchEvent}
* .
*
* @param e
* MotionEvent describing the touch event. All coordinates
* are in the RecyclerView's coordinate system.
*/
/**
* 触摸处理事件分发
*
* @param rv
* @param e
*/
public void onTouchEvent(RecyclerView rv, MotionEvent e);
/**
* Called when a child of RecyclerView does not want RecyclerView and
* its ancestors to intercept touch events with
* {@link ViewGroup#onInterceptTouchEvent(MotionEvent)}.
*
* @param disallowIntercept
* True if the child does not want the parent to intercept
* touch events.
* @see ViewParent#requestDisallowInterceptTouchEvent(boolean)
*/
/**
* 不希望RecycleView拦截事件
*
* @param disallowIntercept
*/
public void onRequestDisallowInterceptTouchEvent(
boolean disallowIntercept);
}
这是一个接口,在里面看到了熟悉的方法onInterceptTouchEvent(这不是ViewGroup的拦截事件吗),onTouchEvent(这不是View的处理事件的方法吗),so那么现在可以猜测,RecycleView是通过这个接口将触摸事件传给ItemTouchHelper 类来处理的,好接下来验证推论
@Override
public boolean onInterceptTouchEvent(MotionEvent e) {
if (mLayoutFrozen) {
// When layout is frozen, RV does not intercept the motion event.
// A child view e.g. a button may still get the click.
return false;
}
if (dispatchOnItemTouchIntercept(e)) {
cancelTouch();
return true;
}
if (mLayout == null) {
return false;
}
下面省略n行
}
if (dispatchOnItemTouchIntercept(e)) {
cancelTouch();
return true;
}
private boolean dispatchOnItemTouchIntercept(MotionEvent e) {
final int action = e.getAction();
if (action == MotionEvent.ACTION_CANCEL
|| action == MotionEvent.ACTION_DOWN) {
mActiveOnItemTouchListener = null;
}
final int listenerCount = mOnItemTouchListeners.size();
for (int i = 0; i < listenerCount; i++) {
final OnItemTouchListener listener = mOnItemTouchListeners.get(i);
// 哪一个返回true,哪一个就等于mActiveOnItemTouchListener
if (listener.onInterceptTouchEvent(this, e)
&& action != MotionEvent.ACTION_CANCEL) {
mActiveOnItemTouchListener = listener;
return true;
}
}
return false;
}
public boolean onTouchEvent(MotionEvent e) {
if (mLayoutFrozen || mIgnoreMotionEventTillDown) {
return false;
}
// 先看看itemView要不要处理itemtouch
if (dispatchOnItemTouch(e)) {
cancelTouch();
return true;
}
省略若干行......
}
private boolean dispatchOnItemTouch(MotionEvent e) {
final int action = e.getAction();
if (mActiveOnItemTouchListener != null) {
if (action == MotionEvent.ACTION_DOWN) {
// Stale state from a previous gesture, we're starting a new
// one. Clear it.
mActiveOnItemTouchListener = null;
} else {
mActiveOnItemTouchListener.onTouchEvent(this, e);
if (action == MotionEvent.ACTION_CANCEL
|| action == MotionEvent.ACTION_UP) {
// Clean up for the next gesture.
mActiveOnItemTouchListener = null;
}
return true;
}
}
// Listeners will have already received the ACTION_DOWN via
// dispatchOnItemTouchIntercept
// as called from onInterceptTouchEvent; skip it.
if (action != MotionEvent.ACTION_DOWN) {
final int listenerCount = mOnItemTouchListeners.size();
for (int i = 0; i < listenerCount; i++) {
final OnItemTouchListener listener = mOnItemTouchListeners
.get(i);
if (listener.onInterceptTouchEvent(this, e)) {
mActiveOnItemTouchListener = listener;
return true;
}
}
}
return false;
}
public boolean onInterceptTouchEvent(RecyclerView recyclerView,
MotionEvent event) {
mGestureDetector.onTouchEvent(event);
if (DEBUG) {
Log.d(TAG,
"intercept: x:" + event.getX() + ",y:" + event.getY()
+ ", " + event);
}
final int action = MotionEventCompat.getActionMasked(event);
if (action == MotionEvent.ACTION_DOWN) {
mActivePointerId = MotionEventCompat.getPointerId(event, 0);
// action=Down的时候按下
mInitialTouchX = event.getX();
// action=Down的时候按下
mInitialTouchY = event.getY();
obtainVelocityTracker();
if (mSelected == null) {
final RecoverAnimation animation = findAnimation(event);
if (animation != null) {
Log.i("huoying", "inter:animation!=null");
mInitialTouchX -= animation.mX;
mInitialTouchY -= animation.mY;
endRecoverAnimation(animation.mViewHolder, true);
// 结束动画时清除mPendingCleanup保存的itemView的集合
if (mPendingCleanup
.remove(animation.mViewHolder.itemView)) {
mCallback.clearView(mRecyclerView,
animation.mViewHolder);
}
select(animation.mViewHolder, animation.mActionState);
// 更新选中的itemView的距离
updateDxDy(event, mSelectedFlags, 0);
}
}
}
// 抬起,取消设置无状态
else if (action == MotionEvent.ACTION_CANCEL
|| action == MotionEvent.ACTION_UP) {
mActivePointerId = ACTIVE_POINTER_ID_NONE;
Log.i("huoying", "inter:up");
select(null, ACTION_STATE_IDLE);
}
// 如果mActivePointerId有效就走判断
else if (mActivePointerId != ACTIVE_POINTER_ID_NONE) {
// in a non scroll orientation, if distance change is above
// threshold, we
// can select the item
final int index = MotionEventCompat.findPointerIndex(event,
mActivePointerId);
if (DEBUG) {
Log.d(TAG, "pointer index " + index);
}
if (index >= 0) {
Log.i("huoying", "inter:ACTIVE_POINTER_ID_NONE");
// 检查是否可滑动
checkSelectForSwipe(action, event, index);
}
}
if (mVelocityTracker != null) {
mVelocityTracker.addMovement(event);
}
return mSelected != null;
}
private boolean checkSelectForSwipe(int action, MotionEvent motionEvent,
int pointerIndex) {
// 当已经有选中的View时,或事件不等于滑动事件,或者mActionState=正在被拖动的状态,或者mCallback不支持滑动直接返回false
if (mSelected != null || action != MotionEvent.ACTION_MOVE
|| mActionState == ACTION_STATE_DRAG
|| !mCallback.isItemViewSwipeEnabled()) {
return false;
}
// 如果当前mRecyclerView的状态是正在拖动的状态返回false
if (mRecyclerView.getScrollState() == RecyclerView.SCROLL_STATE_DRAGGING) {
return false;
}
// 根据触摸事件找到手指放在哪个子View的位置
final ViewHolder vh = findSwipedView(motionEvent);
if (vh == null) {
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;
}
// mDx and mDy are only set in allowed directions. We use custom x/y
// here instead of
// updateDxDy to avoid swiping if user moves more in the other direction
final float x = MotionEventCompat.getX(motionEvent, pointerIndex);
final float y = MotionEventCompat.getY(motionEvent, pointerIndex);
// 得到手指水平竖直移动距离
final float dx = x - mInitialTouchX;
final float dy = y - mInitialTouchY;
// swipe target is chose w/o applying flags so it does not really check
// if swiping in that
// direction is allowed. This why here, we use mDx mDy to check slope
// value again.
final float absDx = Math.abs(dx);
final float absDy = Math.abs(dy);
if (absDx < mSlop && absDy < mSlop) {
return false;
}
// 水平方向移动的距离大的时候
if (absDx > absDy) {
// dx小于0表示手指向左滑动,如果设置的swipeFlags不包括item的话,不做操作
if (dx < 0 && (swipeFlags & LEFT) == 0) {
return false;
}
// 和上面同理
if (dx > 0 && (swipeFlags & RIGHT) == 0) {
return false;
}
} else {
// 和上面同理
if (dy < 0 && (swipeFlags & UP) == 0) {
return false;
}
// 和上面同理
if (dy > 0 && (swipeFlags & DOWN) == 0) {
return false;
}
}
// 当前选中itemView的偏移量归零
mDx = mDy = 0f;
mActivePointerId = MotionEventCompat.getPointerId(motionEvent, 0);
// 满足滑动,设置滑动状态ACTION_STATE_SWIPE
select(vh, ACTION_STATE_SWIPE);
return true;
}
额,代码量也不少,
如果当前被选中的itemView还为空,此事件不是move事件,不是拖撤事件,
Callback
是可以滑动的(
isItemViewSwipeEnabled
默认返回true),那么
public int getMovementFlags(RecyclerView recyclerView, ViewHolder holder) {
int dragFlags = ItemTouchHelper.UP|ItemTouchHelper.DOWN;
int swipeFlags = ItemTouchHelper.LEFT|ItemTouchHelper.RIGHT;
//定义上下为拖动,左右为滑动
int flags = makeMovementFlags(dragFlags, swipeFlags);
return flags;
}
那么现在知道这个方法到底是干啥用的了,告诉ItemTouchHelper什么情况下是拖撤事件,什么情况下是滑动事件,接下来计算滑动是那一个方向的,如果滑动不包括left,right,down,up的话直接返回false,以下判断都满足后清空偏移距离,并调用
select(vh, ACTION_STATE_SWIPE),此时传入的状态是滑动状态。也就是说ItemTouchHelper
ACTION_STATE_DRAG:长按滑动时的拖动状态
好接着看select(vh, ACTION_STATE_SWIPE)方法
if (selected == mSelected && actionState == mActionState) {
return;
}
mDragScrollStartTimeInMs = Long.MIN_VALUE;
final int prevActionState = mActionState;
// prevent duplicate animations
endRecoverAnimation(selected, true);
mActionState = actionState;
// 假如状态是拖动,例如久安的时候
if (actionState == ACTION_STATE_DRAG) {
// we remove after animation is complete. this means we only elevate
// the last drag
// child but that should perform good enough as it is very hard to
// start dragging a
// new child before the previous one settles.
mOverdrawChild = selected.itemView;
addChildDrawingOrderCallback();
}
//
int actionStateMask = (1 << (DIRECTION_FLAG_COUNT + DIRECTION_FLAG_COUNT
* actionState)) - 1;
boolean preventLayout = false;
if (mSelected != null) {
// 上一个选中的mSelected
final ViewHolder prevSelected = mSelected;
if (prevSelected.itemView.getParent() != null) {
final int swipeDir = prevActionState == ACTION_STATE_DRAG ? 0
: swipeIfNecessary(prevSelected);
releaseVelocityTracker();
// find where we should animate to
final float targetTranslateX, targetTranslateY;
int animationType;
switch (swipeDir) {
// 向左滑动,向右滑动
case LEFT:
case RIGHT:
case START:
case END:
// java.lang.Math.signum(double d)
// 如果参数大于零返回1.0,如果参数小于零返回-1,如果参数为0,则返回signum函数的参数为零
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;
}
// 将选中view的移动距离赋值到数组中
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(ValueAnimatorCompat animation) {
super.onAnimationEnd(animation);
if (this.mOverridden) {
return;
}
// 上面计算的swipeDir《=0的时候,就是拖动或者滑动失败的方式
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
mPendingCleanup.add(prevSelected.itemView);
mIsPendingCleanup = true;
if (swipeDir > 0) {
// Animation might be ended by other animators
// during a layout.
// We defer callback to avoid editing adapter
// during a layout.
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 {
removeChildDrawingOrderCallbackIfNecessary(prevSelected.itemView);
mCallback.clearView(mRecyclerView, prevSelected);
}
mSelected = null;
}
if (selected != null) {
mSelectedFlags = (mCallback.getAbsoluteMovementFlags(mRecyclerView,
selected) & actionStateMask) >> (mActionState * DIRECTION_FLAG_COUNT);
// 选中的时候赋值mSelectedStartX=left
mSelectedStartX = selected.itemView.getLeft();
// mSelectedStartY=top
mSelectedStartY = selected.itemView.getTop();
mSelected = selected;
// 如果是拖动
if (actionState == ACTION_STATE_DRAG) {
// 回调长按的反馈
mSelected.itemView
.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
}
}
final ViewParent rvParent = mRecyclerView.getParent();
// 通知RecycleView不拦截子View的事件
if (rvParent != null) {
rvParent.requestDisallowInterceptTouchEvent(mSelected != null);
}
if (!preventLayout) {
// 设置layoutManager条目动画可以执行
mRecyclerView.getLayoutManager()
.requestSimpleAnimationsInNextLayout();
}
// 告诉mCallback选中的View已经改变了
mCallback.onSelectedChanged(mSelected, mActionState);
// 重新绘制,因为temTouchHelper extends
// RecyclerView.ItemDecoration(想实现一些不为人知的秘密)
mRecyclerView.invalidate();
public void onTouchEvent(RecyclerView recyclerView, MotionEvent event) {
// 这个事件就监听长按事件
mGestureDetector.onTouchEvent(event);
if (DEBUG) {
Log.d(TAG, "on touch: x:" + mInitialTouchX + ",y:"
+ mInitialTouchY + ", :" + event);
}
if (mVelocityTracker != null) {
mVelocityTracker.addMovement(event);
}
if (mActivePointerId == ACTIVE_POINTER_ID_NONE) {
return;
}
final int action = MotionEventCompat.getActionMasked(event);
final int activePointerIndex = MotionEventCompat.findPointerIndex(
event, 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) {
Log.i("huoying", "touch:move");
// 先更新位置再移动
// 不断移动的时候改变选中的View的移动距离
updateDxDy(event, mSelectedFlags, activePointerIndex);
// 移动viewHolder
moveIfNecessary(viewHolder);
mRecyclerView.removeCallbacks(mScrollRunnable);
mScrollRunnable.run();
// 最后重画数据
mRecyclerView.invalidate();
}
break;
}
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
if (mVelocityTracker != null) {
mVelocityTracker.computeCurrentVelocity(1000,
mRecyclerView.getMaxFlingVelocity());
}
Log.i("huoying", "touch:up");
select(null, ACTION_STATE_IDLE);
mActivePointerId = ACTIVE_POINTER_ID_NONE;
break;
case MotionEvent.ACTION_POINTER_UP: {
final int pointerIndex = MotionEventCompat
.getActionIndex(event);
final int pointerId = MotionEventCompat.getPointerId(event,
pointerIndex);
if (pointerId == mActivePointerId) {
if (mVelocityTracker != null) {
mVelocityTracker.computeCurrentVelocity(1000,
mRecyclerView.getMaxFlingVelocity());
}
// This was our active pointer going up. Choose a new
// active pointer and adjust accordingly.
final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
mActivePointerId = MotionEventCompat.getPointerId(event,
newPointerIndex);
updateDxDy(event, mSelectedFlags, pointerIndex);
}
break;
}
}
}
既然拦截事件已经帮我们确定了要移动的itemView是谁了,那么onTouchEvent只要处理移动就好了,从效果图中可以看出左右滑动时,选中的itemView会左右移动并伴随着缩放效果,在ACTION_MOVE事件中updateDxDy(event, mSelectedFlags, activePointerIndex)用于不断改变手指离按下偏移了多少距离,然后调用moveIfNecessary方法,最后又调用了mRecyclerView.invalidate(),看来移动ItemView并不是简单的移动,它的操作可能在Ondraw方法中。接着看一下这三个方法
private void updateDxDy(MotionEvent ev, int directionFlags, int pointerIndex) {
final float x = MotionEventCompat.getX(ev, pointerIndex);
final float y = MotionEventCompat.getY(ev, pointerIndex);
// Calculate the distance moved
mDx = x - mInitialTouchX;
mDy = y - mInitialTouchY;
if ((directionFlags & LEFT) == 0) {
mDx = Math.max(0, mDx);
}
if ((directionFlags & RIGHT) == 0) {
mDx = Math.min(0, mDx);
}
if ((directionFlags & UP) == 0) {
mDy = Math.max(0, mDy);
}
if ((directionFlags & DOWN) == 0) {
mDy = Math.min(0, mDy);
}
}
if (mRecyclerView.isLayoutRequested()) {
return;
}
if (mActionState != ACTION_STATE_DRAG) {
return;
}
final float threshold = mCallback.getMoveThreshold(viewHolder);
// 计算新的位置的left,top
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;
}
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();
// 回调mCallback的onMove方法
if (mCallback.onMove(mRecyclerView, viewHolder, target)) {
// keep target visible
mCallback.onMoved(mRecyclerView, viewHolder, fromPosition, target,
toPosition, x, y);
}
}
这个方法首先判断一下是否是拖动的状态,不是则直接返回,说明这个方法是为拖动状态准备的,看完滑动再看拖动,那么最后只剩下重画方法可以用来执行位置的偏移了,那么进入RecycleView看看它的onDraw方法
public void onDraw(Canvas c) {
super.onDraw(c);
final int count = mItemDecorations.size();
for (int i = 0; i < count; i++) {
mItemDecorations.get(i).onDraw(c, this, mState);
}
}
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);
}
private 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 < recoverAnimSize; i++) {
final ItemTouchHelper.RecoverAnimation anim = recoverAnimationList
.get(i);
// 在画的时候改变里面的值
anim.update();
final int count = c.save();
// 最后以anim.mX, anim.mY为最终的偏移值
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);
}
}
@Override
public void onChildDraw(Canvas c, RecyclerView recyclerView,
ViewHolder viewHolder, float dX, float dY, int actionState,
boolean isCurrentlyActive) {
if(actionState==ItemTouchHelper.ACTION_STATE_SWIPE){
float alpha = 1-Math.abs(dX)/viewHolder.itemView.getWidth();
viewHolder.itemView.setAlpha(alpha);//1~0
viewHolder.itemView.setScaleX(alpha);//1~0
viewHolder.itemView.setScaleY(alpha);//1~0
}
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState,
isCurrentlyActive);
}
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);
}
static {
if (Build.VERSION.SDK_INT >= 21) {
sUICallback = new ItemTouchUIUtilImpl.Lollipop();
} else if (Build.VERSION.SDK_INT >= 11) {
sUICallback = new ItemTouchUIUtilImpl.Honeycomb();
} else {
sUICallback = new ItemTouchUIUtilImpl.Gingerbread();
}
}
根据版本号生成不一样的工具类,当小于11时
private void draw(Canvas c, RecyclerView parent, View view, float dX,
float dY) {
c.save();
c.translate(dX, dY);
parent.drawChild(c, view, 0);
c.restore();
}
版本号小于11的时候通过移动画布,来达到选中的itemView的移动效果,那么大于11的呢
public void onDraw(Canvas c, RecyclerView recyclerView, View view,
float dX, float dY, int actionState, boolean isCurrentlyActive) {
// 直接设置距离
ViewCompat.setTranslationX(view, dX);
ViewCompat.setTranslationY(view, dY);
}
public void onLongPress(MotionEvent e) {
Log.i("huoying", "长按");
View child = findChildView(e);
if (child != null) {
ViewHolder vh = mRecyclerView.getChildViewHolder(child);
if (vh != null) {
//判断callBack中有没有设置DragFlag
if (!mCallback.hasDragFlag(mRecyclerView, vh)) {
return;
}
int pointerId = MotionEventCompat.getPointerId(e, 0);
// Long press is deferred.
// Check w/ active pointer id to avoid selecting after
// motion
// event is canceled.
if (pointerId == mActivePointerId) {
final int index = MotionEventCompat.findPointerIndex(e,
mActivePointerId);
final float x = MotionEventCompat.getX(e, index);
final float y = MotionEventCompat.getY(e, 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);
}
}
}
}
}
}
private void moveIfNecessary(ViewHolder viewHolder) {
if (mRecyclerView.isLayoutRequested()) {
return;
}
if (mActionState != ACTION_STATE_DRAG) {
return;
}
final float threshold = mCallback.getMoveThreshold(viewHolder);
// 计算新的位置的left,top
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;
}
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();
// 回调mCallback的onMove方法
if (mCallback.onMove(mRecyclerView, viewHolder, target)) {
// keep target visible
mCallback.onMoved(mRecyclerView, viewHolder, fromPosition, target,
toPosition, x, y);
}
}
这个方法根据选中的itemView的将要偏移的值,算出和其他的itemView那些发生了碰撞,如果发生了碰撞的话,回调
mCallback.onMove
方法,这个方法也是我们自己实现的
public boolean onMove(RecyclerView recyclerView, ViewHolder srcHolder, ViewHolder targetHolder) {
//如果它们的viewType不一样不让他们替换
if(srcHolder.getItemViewType()!=targetHolder.getItemViewType()){
return false;
}
boolean result = moveListener.onItemMove(srcHolder.getAdapterPosition(), targetHolder.getAdapterPosition());
return result;
}
public boolean onItemMove(int fromPosition, int toPosition) {
// TODO Auto-generated method stub
//交换集合的两个位置,通知RecycleView交换position
Collections.swap(messagemain_list, fromPosition, toPosition);
notifyItemMoved(fromPosition, toPosition);
return true;
}
if (mVelocityTracker != null) {
mVelocityTracker.computeCurrentVelocity(1000,
mRecyclerView.getMaxFlingVelocity());
}
Log.i("huoying", "touch:up");
select(null, ACTION_STATE_IDLE);
public void onAnimationEnd(ValueAnimatorCompat animation) {
super.onAnimationEnd(animation);
if (this.mOverridden) {
return;
}
// 上面计算的swipeDir《=0的时候,就是拖动或者滑动失败的方式
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
mPendingCleanup.add(prevSelected.itemView);
mIsPendingCleanup = true;
if (swipeDir > 0) {
// Animation might be ended by other animators
// during a layout.
// We defer callback to avoid editing adapter
// during a layout.
postDispatchSwipe(this, swipeDir);
}
}
// removed from the list after it is drawn for the last
// time
if (mOverdrawChild == prevSelected.itemView) {
removeChildDrawingOrderCallbackIfNecessary(prevSelected.itemView);
}
}
而这个动画的最后将回调动画结束回调,如果此时滑动超过了子view宽度的一半的话就会回调postDispatchSwipe方法
mRecyclerView.post(new Runnable() {
@Override
public void run() {
if (mRecyclerView != null
&& mRecyclerView.isAttachedToWindow()
&& !anim.mOverridden
&& anim.mViewHolder.getAdapterPosition() != 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.
// 防止ItemAnimator的影响
if ((animator == null || !animator.isRunning(null))
&& !hasRunningRecoverAnim()) {
// 滑动完成调用的方法
Log.i("huoying", "swiped");
mCallback.onSwiped(anim.mViewHolder, swipeDir);
} else {
mRecyclerView.post(this);
}
}
}
});
}
public void onSwiped(ViewHolder holder, int arg1) {
moveListener.onItemRemove(holder.getAdapterPosition());
}
分析到这ItemTouchHelper的思路已经通了,看大体的调用图
总结:
1、ItemTouchHelper将OnItemTouchListener注册进RecyclerView,将自身的ItemDecoration注册进RecyclerView
2、RecyclerView接收到触摸事件的时候先把事件交给ItemTouchHelper里的OnItemTouchListener处理
3、OnItemTouchListener在onInterceptTouchEvent中调用select()方法找到那个itemView可以被标记为选中
4、OnItemTouchListener在onTouchEvent不断通过updateDxDy(event, mSelectedFlags, activePointerIndex)方法计算偏移量,如果是拖动通过moveIfNecessary()方法计算有满足的碰撞吗,有的话就回调onMove方法交换位置,最后执行mRecyclerView.invalidate()重写画图
5、在RecyclerView的重画onDraw()方法中调用ItemTouchHelper的onDraw方法,最后回调Callback的onChildDraw方法将选中的itemView进行偏移