写在前面的几句话
<p>
上一篇文章已经对ItemTouchHelper的简单使用有了介绍,那么这一篇我们对ItemTouchHelper进行更加深入的了解,对源码进行简单分析
ItemTouchHelper.CallBack分析
<p>
分析ItemTouchHelper之前,我们先看下CallBack的定义了那些方法
//声明不同状态下可以移动的方向(idle, swiping, dragging)
@Override
public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
final int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
final int swipeFlags = ItemTouchHelper.START | ItemTouchHelper.END;
return makeMovementFlags(dragFlags, swipeFlags);
}
//拖动的项目从旧位置移动到新位置时调用
@Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
return false;
}
//滑动到消失后的调用
@Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
}
//是否可以把拖动的ViewHolder拖动到目标ViewHolder之上
@Override
public boolean canDropOver(RecyclerView recyclerView, RecyclerView.ViewHolder current, RecyclerView.ViewHolder target) {
return true;
}
//获取拖动
@Override
public RecyclerView.ViewHolder chooseDropTarget(RecyclerView.ViewHolder selected, List<RecyclerView.ViewHolder> dropTargets, int curX, int curY) {
return dropTargets.get(0);
}
//调用时与元素的用户交互已经结束,也就是它也完成了它的动画时候
@Override
public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
super.clearView(recyclerView, viewHolder);
}
@Override
public int convertToAbsoluteDirection(int flags, int layoutDirection) {
return super.convertToAbsoluteDirection(flags, layoutDirection);
}
//设置手指离开后ViewHolder的动画时间
@Override
public long getAnimationDuration(RecyclerView recyclerView, int animationType, float animateDx, float animateDy) {
return super.getAnimationDuration(recyclerView, animationType, animateDx, animateDy);
}
@Override
public int getBoundingBoxMargin() {
return super.getBoundingBoxMargin();
}
//返回值作为用户视为拖动的距离
@Override
public float getMoveThreshold(RecyclerView.ViewHolder viewHolder) {
return super.getMoveThreshold(viewHolder);
}
//返回值滑动消失的距离,滑动小于这个值不消失,大于消失
@Override
public float getSwipeEscapeVelocity(float defaultValue) {
return super.getSwipeEscapeVelocity(defaultValue);
}
//返回值滑动消失的距离, 这里是相对于RecycleView的宽度,0.5f表示为RecycleView的宽度的一半,取值为0~1f之间
@Override
public float getSwipeThreshold(RecyclerView.ViewHolder viewHolder) {
return super.getSwipeThreshold(viewHolder);
}
//返回值作为滑动的流程程度,越小越难滑动,越大越好滑动
@Override
public float getSwipeVelocityThreshold(float defaultValue) {
return 1f;
}
//当用户拖动一个视图出界的ItemTouchHelper调用
@Override
public int interpolateOutOfBoundsScroll(RecyclerView recyclerView, int viewSize, int viewSizeOutOfBounds, int totalSize, long msSinceStartScroll) {
return super.interpolateOutOfBoundsScroll(recyclerView, viewSize, viewSizeOutOfBounds, totalSize, msSinceStartScroll);
}
//返回值决定是否有滑动操作
@Override
public boolean isItemViewSwipeEnabled() {
return super.isItemViewSwipeEnabled();
}
//返回值决定是否有拖动操作
@Override
public boolean isLongPressDragEnabled() {
return super.isLongPressDragEnabled();
}
//自定义拖动与滑动交互
@Override
public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
}
//自定义拖动与滑动交互
@Override
public void onChildDrawOver(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
super.onChildDrawOver(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
}
//当onMove return ture的时候调用
@Override
public void onMoved(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, int fromPos, RecyclerView.ViewHolder target, int toPos, int x, int y) {
super.onMoved(recyclerView, viewHolder, fromPos, target, toPos, x, y);
}
//当拖动或者滑动的ViewHolder改变时调用
@Override
public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {
super.onSelectedChanged(viewHolder, actionState);
}
结合这些分析注释就明白了上面一篇文章里面CallBack为什么写那些方法了,还有部分的方法还是没有理解到底是干嘛的所有就没有注释了。
ItemTouchHelper相关分析
<p>
上一篇文章中把ItemTouchHelper与RecycleView以及CallBack建立连接的方法如下
ItemTouchHelper mItemTouchHelper = new ItemTouchHelper(new ItemTouchHelperCallback());
mItemTouchHelper.attachToRecyclerView(mRecyclerView);
那么从这个将ItemTouchHelper与RecycleView建立的方法进行分析
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();
}
}
这部分的代码其实没有做太多的事情,无非是获取一些默认值,setupCallbacks()与destroyCallbacks()两个方法,这两个方法从名称看就是相对立的,所以分析一个就好了
destroyCallbacks()
private void destroyCallbacks() {
mRecyclerView.removeItemDecoration(this);
mRecyclerView.removeOnItemTouchListener(mOnItemTouchListener);
mRecyclerView.removeOnChildAttachStateChangeListener(this);
// clean all attached
final int recoverAnimSize = mRecoverAnimations.size();
for (int i = recoverAnimSize - 1; i >= 0; i--) {
final RecoverAnimation recoverAnimation = mRecoverAnimations.get(0);
mCallback.clearView(mRecyclerView, recoverAnimation.mViewHolder);
}
mRecoverAnimations.clear();
mOverdrawChild = null;
mOverdrawChildPosition = -1;
releaseVelocityTracker();
}
setupCallbacks()
private void setupCallbacks() {
ViewConfiguration vc = ViewConfiguration.get(mRecyclerView.getContext());
mSlop = vc.getScaledTouchSlop();
mRecyclerView.addItemDecoration(this);
mRecyclerView.addOnItemTouchListener(mOnItemTouchListener);
mRecyclerView.addOnChildAttachStateChangeListener(this);
initGestureDetector();
}
这里的步骤有点多了
分布来说明
1.addItemDecoration(this)
这个方法其实是调用了ItemDecoration的接口.从ItemTouchHelper方法声明部分也可以看到
public class ItemTouchHelper extends RecyclerView.ItemDecoration
implements RecyclerView.OnChildAttachStateChangeListener {}
在ItemTouchHelper中重写了ItemDecoration接口的两个方法如下
@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);
}
从方法可以看到这里其实没有做什么特别的工作,只是回调了Callback的两个回调方法onDrawOver()与onDraw()而这两个方法是Callback的private方法如下
private void onDraw(Canvas c, RecyclerView parent, ViewHolder selected,
List<ItemTouchHelper.RecoverAnimation> 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();
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);
}
}
private void onDrawOver(Canvas c, RecyclerView parent, ViewHolder selected,
List<ItemTouchHelper.RecoverAnimation> 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);
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();
}
}
这里牵扯的东西比较多,暂时不分析,我们可以看到有两个方法分别为onChildDrawOver()与onChildDraw()
调用了这两个方法,接下来看下这两个方法里面有什么?
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);
}
public void onChildDrawOver(Canvas c, RecyclerView recyclerView,
ViewHolder viewHolder,
float dX, float dY, int actionState, boolean isCurrentlyActive) {
sUICallback.onDrawOver(c, recyclerView, viewHolder.itemView, dX, dY, actionState,
isCurrentlyActive);
}
这里面其实是sUICallback的回调方法
这里的sUICallback是一个接口,根据不同的版本执行不同的onDraw与onDrawOver方法
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();
}
}
所以在我们自定义的CallBack中可以取重写onChildDraw()与onChildDrawOver()方法来实现自定义的拖动与滑动交互
2.addOnChildAttachStateChangeListener(this)
这里调用了OnChildAttachStateChangeListener这个接口,这个接口里有两个方法,分别是在RecycleView添加一个View与删除一个View的时候回调
那看看我们在ItemTouchHelper中重写的方法
@Override
public void onChildViewAttachedToWindow(View view) {
}
@Override
public void onChildViewDetachedFromWindow(View view) {
removeChildDrawingOrderCallbackIfNecessary(view);
final ViewHolder holder = mRecyclerView.getChildViewHolder(view);
if (holder == null) {
return;
}
if (mSelected != null && holder == mSelected) {
select(null, ACTION_STATE_IDLE);
} else {
endRecoverAnimation(holder, false); // this may push it into pending cleanup list.
if (mPendingCleanup.remove(holder.itemView)) {
mCallback.clearView(mRecyclerView, holder);
}
}
}
因为ItemTouchHelper中只用考虑移除的情况,
这里面的方法暂时不介绍,可以看到回调了clearView()的方法,所以在元素的用户交互已经结束的时候,可以通过这个方法监听到
3.initGestureDetector()
这里主要是初始化GestureDetector
private class ItemTouchHelperGestureListener extends GestureDetector.SimpleOnGestureListener {
@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 = 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);
}
}
}
}
}
}
首先看下Callback调用了hasDragFlag这个方法,那我们看下这个方法
private boolean hasDragFlag(RecyclerView recyclerView, ViewHolder viewHolder) {
final int flags = getAbsoluteMovementFlags(recyclerView, viewHolder);
return (flags & ACTION_MODE_DRAG_MASK) != 0;
}
final int getAbsoluteMovementFlags(RecyclerView recyclerView,
ViewHolder viewHolder) {
final int flags = getMovementFlags(recyclerView, viewHolder);
return convertToAbsoluteDirection(flags, ViewCompat.getLayoutDirection(recyclerView));
}
经过调用发现最后调用了getMovementFlags这个方法,所以我们重写方法如果是没有声明的在onLongPress中就直接return了,不会触发下面的方法了
再往下看,会调用Callback的isLongPressDragEnabled()方法,当return为true的时候会执行select()方法
4. addOnItemTouchListener
这里则是调用了RecycleView的addOnItemTouchListener方法,ItemTouchHelper重写了OnItemTouchListener接口的方法,OnItemTouchListener有三个方法,我们一个个来进行分析
(1).onInterceptTouchEvent
public boolean onInterceptTouchEvent(RecyclerView recyclerView, MotionEvent event) {
//给前面注册的GestureDetector添加监听
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) {
//获取这个事件对应的pointerId,ViewDragerHelper中也有说明
mActivePointerId = MotionEventCompat.getPointerId(event, 0);
mInitialTouchX = event.getX();
mInitialTouchY = event.getY();
//初始化跟踪触摸屏类VelocityTracker
obtainVelocityTracker();
if (mSelected == null) {
//根据当前的MotionEvent查找RecoverAnimation对象
final RecoverAnimation animation = findAnimation(event);
//如果animation存在则更新animation
if (animation != null) {
mInitialTouchX -= animation.mX;
mInitialTouchY -= animation.mY;
//删除RecoverAnimation
endRecoverAnimation(animation.mViewHolder, true);
if (mPendingCleanup.remove(animation.mViewHolder.itemView)) {
mCallback.clearView(mRecyclerView, animation.mViewHolder);
}
//select方法
select(animation.mViewHolder, animation.mActionState);
//更新Dx与Dy
updateDxDy(event, mSelectedFlags, 0);
}
}
} else if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
mActivePointerId = ACTIVE_POINTER_ID_NONE;
//select方法
select(null, ACTION_STATE_IDLE);
} 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);
}
//index >= 0 表示最少有一个触控点存在
if (index >= 0) {
checkSelectForSwipe(action, event, index);
}
}
if (mVelocityTracker != null) {
mVelocityTracker.addMovement(event);
}
return mSelected != null;
}
大部分注释都已经说明了,主要把他们调用的方法来进行说明
首先来看下RecoverAnimation这个类,这个类中有ValueAnimatorCompat主要是根据起始点及ActionState等做动画的。
接下来就贴出这个类
private class RecoverAnimation implements AnimatorListenerCompat {
final float mStartDx;
final float mStartDy;
final float mTargetX;
final float mTargetY;
final ViewHolder mViewHolder;
final int mActionState;
private final ValueAnimatorCompat mValueAnimator;
private final int mAnimationType;
public boolean mIsPendingCleanup;
float mX;
float mY;
// if user starts touching a recovering view, we put it into interaction mode again,
// instantly.
boolean mOverridden = false;
private boolean mEnded = false;
private float mFraction;
public RecoverAnimation(ViewHolder viewHolder, int animationType,
int actionState, float startDx, float startDy, float targetX, float targetY) {
mActionState = actionState;
mAnimationType = animationType;
mViewHolder = viewHolder;
mStartDx = startDx;
mStartDy = startDy;
mTargetX = targetX;
mTargetY = targetY;
mValueAnimator = AnimatorCompatHelper.emptyValueAnimator();
mValueAnimator.addUpdateListener(
new AnimatorUpdateListenerCompat() {
@Override
public void onAnimationUpdate(ValueAnimatorCompat animation) {
setFraction(animation.getAnimatedFraction());
}
});
mValueAnimator.setTarget(viewHolder.itemView);
mValueAnimator.addListener(this);
setFraction(0f);
}
public void setDuration(long duration) {
mValueAnimator.setDuration(duration);
}
public void start() {
mViewHolder.setIsRecyclable(false);
mValueAnimator.start();
}
public void cancel() {
mValueAnimator.cancel();
}
public void setFraction(float fraction) {
mFraction = fraction;
}
/**
* We run updates on onDraw method but use the fraction from animator callback.
* This way, we can sync translate x/y values w/ the animators to avoid one-off frames.
*/
public void update() {
if (mStartDx == mTargetX) {
mX = ViewCompat.getTranslationX(mViewHolder.itemView);
} else {
mX = mStartDx + mFraction * (mTargetX - mStartDx);
}
if (mStartDy == mTargetY) {
mY = ViewCompat.getTranslationY(mViewHolder.itemView);
} else {
mY = mStartDy + mFraction * (mTargetY - mStartDy);
}
}
@Override
public void onAnimationStart(ValueAnimatorCompat animation) {
}
@Override
public void onAnimationEnd(ValueAnimatorCompat animation) {
if (!mEnded) {
mViewHolder.setIsRecyclable(true);
}
mEnded = true;
}
@Override
public void onAnimationCancel(ValueAnimatorCompat animation) {
setFraction(1f); //make sure we recover the view's state.
}
@Override
public void onAnimationRepeat(ValueAnimatorCompat animation) {
}
}
里面的成员变量大家看名称应该大部分就可以理解了,不做更多的说明了
那么来看看findAnimation()这个方法
//根据查找的View从mRecoverAnimations集合中查找相同View的RecoverAnimation
private RecoverAnimation findAnimation(MotionEvent event) {
if (mRecoverAnimations.isEmpty()) {
return null;
}
View target = findChildView(event);
for (int i = mRecoverAnimations.size() - 1; i >= 0; i--) {
final RecoverAnimation anim = mRecoverAnimations.get(i);
if (anim.mViewHolder.itemView == target) {
return anim;
}
}
return null;
}
//根据event查找View
private View findChildView(MotionEvent event) {
// first check elevated views, if none, then call RV
final float x = event.getX();
final float y = event.getY();
//mSelected不为空则拿mSelected.itemView
if (mSelected != null) {
final View selectedView = mSelected.itemView;
if (hitTest(selectedView, x, y, mSelectedStartX + mDx, mSelectedStartY + mDy)) {
return selectedView;
}
}
//从mRecoverAnimations这个RecoverAnimation类集合中查找是否存在View
for (int i = mRecoverAnimations.size() - 1; i >= 0; i--) {
final RecoverAnimation anim = mRecoverAnimations.get(i);
final View view = anim.mViewHolder.itemView;
if (hitTest(view, x, y, anim.mX, anim.mY)) {
return view;
}
}
//通过RecyclerView的findChildViewUnder方法查找View
return mRecyclerView.findChildViewUnder(x, y);
}
//计算是否点击在当前View的区域内
private static boolean hitTest(View child, float x, float y, float left, float top) {
return x >= left &&
x <= left + child.getWidth() &&
y >= top &&
y <= top + child.getHeight();
}
往下看就可以看到endRecoverAnimation方法,这个方法主要就是从mRecoverAnimations集合中删除某个RecoverAnimation对象
private int endRecoverAnimation(ViewHolder viewHolder, boolean override) {
final int recoverAnimSize = mRecoverAnimations.size();
for (int i = recoverAnimSize - 1; i >= 0; i--) {
final RecoverAnimation anim = mRecoverAnimations.get(i);
if (anim.mViewHolder == viewHolder) {
anim.mOverridden |= override;
if (!anim.mEnded) {
anim.cancel();
}
mRecoverAnimations.remove(i);
return anim.mAnimationType;
}
}
return 0;
}
接下来看下select()方法,这个方法作用是开始拖动或者滑动指定的View
private void select(ViewHolder selected, int actionState) {
//当ViewHolder一致且State状态一致时候直接返回,不做处理
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;
//当mSelected不为null的时候,新建RecoverAnimation对象,并且start这个ValueAnimator
if (mSelected != null) {
final ViewHolder prevSelected = mSelected;
if (prevSelected.itemView.getParent() != null) {
//当为DRAG状态时候swipeDir为0
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:
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;
}
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则drag与swipe失败,Callback会调用clearView方法
//swipeDir >0则表示成功,会调用postDispatchSwipe方法
//当为DRAG状态时候因为swipeDir为0,所以只走clearView方法
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) {
// 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);
}
}
};
//获取AnimationDuration,我们可以通过重写这个方法来设定动画的时间
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;
}
//当传进来的selected不为空的时候将selected赋给mSelected
if (selected != null) {
mSelectedFlags =
(mCallback.getAbsoluteMovementFlags(mRecyclerView, selected) & actionStateMask)
>> (mActionState * DIRECTION_FLAG_COUNT);
mSelectedStartX = selected.itemView.getLeft();
mSelectedStartY = selected.itemView.getTop();
mSelected = selected;
if (actionState == ACTION_STATE_DRAG) {
mSelected.itemView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
}
}
final ViewParent rvParent = mRecyclerView.getParent();
if (rvParent != null) {
rvParent.requestDisallowInterceptTouchEvent(mSelected != null);
}
if (!preventLayout) {
mRecyclerView.getLayoutManager().requestSimpleAnimationsInNextLayout();
}
//每次select会带来拖动或者滑动的ViewHolder改变,所以这里会调用onSelectedChanged方法,我们可以通过回调接受到这些信息
mCallback.onSelectedChanged(mSelected, mActionState);
mRecyclerView.invalidate();
}
private 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.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.
if ((animator == null || !animator.isRunning(null))
&& !hasRunningRecoverAnim()) {
//当滑动结束会调用onSwiped()方法,我们可以在
mCallback.onSwiped(anim.mViewHolder, swipeDir);
} else {
mRecyclerView.post(this);
}
}
}
});
}
通过代码这部分代码我们可以大致知道了select这个方法的作用了,它主要是处理当手指拖动或者滑动结束后的动画,要通过两次调用,第一次在我们选中的时候,作用是确定我们手指选择的View,第二次在我们手指放开的时候,作用是给这个View设置动画,并且执行。
接下来看看checkSelectForSwipe() 方法
private boolean checkSelectForSwipe(int action, MotionEvent motionEvent, int pointerIndex) {
//这里调用了Callback的isItemViewSwipeEnabled()方法,我们通过重写这个方法可以控制是否可以Swipe
if (mSelected != null || action != MotionEvent.ACTION_MOVE
|| mActionState == ACTION_STATE_DRAG || !mCallback.isItemViewSwipeEnabled()) {
return false;
}
if (mRecyclerView.getScrollState() == RecyclerView.SCROLL_STATE_DRAGGING) {
return false;
}
final ViewHolder vh = findSwipedView(motionEvent);
if (vh == null) {
return false;
}
//这里的getAbsoluteMovementFlags()前面有介绍过,
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);
// Calculate the distance moved
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) {
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;
}
}
mDx = mDy = 0f;
mActivePointerId = MotionEventCompat.getPointerId(motionEvent, 0);
//select方法
select(vh, ACTION_STATE_SWIPE);
return true;
}
这个方法主要是确定当前的我们选择的View是否可以滑动,而前面说的select方法的第一次调用是在这里,这个主要是滑动的select第一个方法,拖动的第一个select方法则在GestureDetector的onLongPress中
到这里基本就介绍结束onInterceptTouchEvent里面做的东西了
(2).onTouchEvent
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) {
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,触发动画
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) {
// 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;
}
}
}
在ACTION_UP时候触发的第二次select()则会执行动画效果
而ACTION_MOVE则是处理随着手指运动的效果,那我们看下里面的实现方法
主要有两个一个是moveIfNecessary另外一个是mScrollRunnable
我们看下mScrollRunnable
private final Runnable mScrollRunnable = new Runnable() {
@Override
public void run() {
if (mSelected != null && scrollIfNecessary()) {
if (mSelected != null) { //it might be lost during scrolling
moveIfNecessary(mSelected);
}
mRecyclerView.removeCallbacks(mScrollRunnable);
ViewCompat.postOnAnimation(mRecyclerView, this);
}
}
};
那我们先来分析下scrollIfNecessary()然后再分析moveIfNecessary()方法
scrollIfNecessary其实上面的注释解释的很清楚,它的作用是检测我们滑动是否到达RecycleView的边缘区域,如果到达边缘区域则将RecycleView移动(scrollBy),这里也调用了callback的interpolateOutOfBoundsScroll方法,所以我们可以在这里监听到我们拖出视图边界的调用
接下来看一下moveIfNecessary()方法
private void moveIfNecessary(ViewHolder viewHolder) {
if (mRecyclerView.isLayoutRequested()) {
return;
}
if (mActionState != ACTION_STATE_DRAG) {
return;
}
//获取getMoveThreshold,可以通过重写来自定义用户视为拖动的距离
final float threshold = mCallback.getMoveThreshold(viewHolder);
final int x = (int) (mSelectedStartX + mDx);
final int y = (int) (mSelectedStartY + mDy);
//当移动距离小于拖动距离,return掉
if (Math.abs(y - viewHolder.itemView.getTop()) < viewHolder.itemView.getHeight() * threshold
&& Math.abs(x - viewHolder.itemView.getLeft())
< viewHolder.itemView.getWidth() * threshold) {
return;
}
List<ViewHolder> swapTargets = findSwapTargets(viewHolder);
if (swapTargets.size() == 0) {
return;
}
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();
//调用onMove,可以重写来让RecycleView改变,也可以让callback的onMoved方法是否重调
if (mCallback.onMove(mRecyclerView, viewHolder, target)) {
// keep target visible
mCallback.onMoved(mRecyclerView, viewHolder, fromPosition,
target, toPosition, x, y);
}
}
所以这里其实是用来判断是否move的带来RecycleView的变化
最后 调用了mRecyclerView.invalidate()方法,而这个方法呢则调用了前面所提到的ItemDecoration里的方法,而那里面的方法处理的是当运动带来的拖动与滑动的交互,前面有提到就不做过多的介绍了
所以总结一下,拖动时通过mRecyclerView.invalidate让onDraw不断重绘带来手指与点击ViewHolder的变化,当手指离开时候则通过select方法启动RecoverAnimation让ViewHolder执行后面的动画,基本的流程就是这样,当然中间有很多Callback方法带来不同的变化,感觉整个流程我还是讲的比较乱的,大家理解大致流程就好了。。到这里就基本分析结束了,当然还有一些方法可能没有介绍了,大家可以通过前面的Callback注释的方法去理解大致的功能就好了。
网友评论