寫在前面的幾句話
<p>
上一篇文章已經(jīng)對(duì)ItemTouchHelper的簡(jiǎn)單使用有了介紹,那么這一篇我們對(duì)ItemTouchHelper進(jìn)行更加深入的了解,對(duì)源碼進(jìn)行簡(jiǎn)單分析
ItemTouchHelper.CallBack分析
<p>
分析ItemTouchHelper之前,我們先看下CallBack的定義了那些方法
//聲明不同狀態(tài)下可以移動(dòng)的方向(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);
}
//拖動(dòng)的項(xiàng)目從舊位置移動(dòng)到新位置時(shí)調(diào)用
@Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
return false;
}
//滑動(dòng)到消失后的調(diào)用
@Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
}
//是否可以把拖動(dòng)的ViewHolder拖動(dòng)到目標(biāo)ViewHolder之上
@Override
public boolean canDropOver(RecyclerView recyclerView, RecyclerView.ViewHolder current, RecyclerView.ViewHolder target) {
return true;
}
//獲取拖動(dòng)
@Override
public RecyclerView.ViewHolder chooseDropTarget(RecyclerView.ViewHolder selected, List<RecyclerView.ViewHolder> dropTargets, int curX, int curY) {
return dropTargets.get(0);
}
//調(diào)用時(shí)與元素的用戶交互已經(jīng)結(jié)束,也就是它也完成了它的動(dòng)畫時(shí)候
@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);
}
//設(shè)置手指離開后ViewHolder的動(dòng)畫時(shí)間
@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();
}
//返回值作為用戶視為拖動(dòng)的距離
@Override
public float getMoveThreshold(RecyclerView.ViewHolder viewHolder) {
return super.getMoveThreshold(viewHolder);
}
//返回值滑動(dòng)消失的距離,滑動(dòng)小于這個(gè)值不消失,大于消失
@Override
public float getSwipeEscapeVelocity(float defaultValue) {
return super.getSwipeEscapeVelocity(defaultValue);
}
//返回值滑動(dòng)消失的距離, 這里是相對(duì)于RecycleView的寬度,0.5f表示為RecycleView的寬度的一半,取值為0~1f之間
@Override
public float getSwipeThreshold(RecyclerView.ViewHolder viewHolder) {
return super.getSwipeThreshold(viewHolder);
}
//返回值作為滑動(dòng)的流程程度,越小越難滑動(dòng),越大越好滑動(dòng)
@Override
public float getSwipeVelocityThreshold(float defaultValue) {
return 1f;
}
//當(dāng)用戶拖動(dòng)一個(gè)視圖出界的ItemTouchHelper調(diào)用
@Override
public int interpolateOutOfBoundsScroll(RecyclerView recyclerView, int viewSize, int viewSizeOutOfBounds, int totalSize, long msSinceStartScroll) {
return super.interpolateOutOfBoundsScroll(recyclerView, viewSize, viewSizeOutOfBounds, totalSize, msSinceStartScroll);
}
//返回值決定是否有滑動(dòng)操作
@Override
public boolean isItemViewSwipeEnabled() {
return super.isItemViewSwipeEnabled();
}
//返回值決定是否有拖動(dòng)操作
@Override
public boolean isLongPressDragEnabled() {
return super.isLongPressDragEnabled();
}
//自定義拖動(dòng)與滑動(dòng)交互
@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);
}
//自定義拖動(dòng)與滑動(dòng)交互
@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);
}
//當(dāng)onMove return ture的時(shí)候調(diào)用
@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);
}
//當(dāng)拖動(dòng)或者滑動(dòng)的ViewHolder改變時(shí)調(diào)用
@Override
public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {
super.onSelectedChanged(viewHolder, actionState);
}
結(jié)合這些分析注釋就明白了上面一篇文章里面CallBack為什么寫那些方法了,還有部分的方法還是沒有理解到底是干嘛的所有就沒有注釋了。
ItemTouchHelper相關(guān)分析
<p>
上一篇文章中把ItemTouchHelper與RecycleView以及CallBack建立連接的方法如下
ItemTouchHelper mItemTouchHelper = new ItemTouchHelper(new ItemTouchHelperCallback());
mItemTouchHelper.attachToRecyclerView(mRecyclerView);
那么從這個(gè)將ItemTouchHelper與RecycleView建立的方法進(jìn)行分析
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();
}
}
這部分的代碼其實(shí)沒有做太多的事情,無非是獲取一些默認(rèn)值,setupCallbacks()與destroyCallbacks()兩個(gè)方法,這兩個(gè)方法從名稱看就是相對(duì)立的,所以分析一個(gè)就好了
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();
}
這里的步驟有點(diǎn)多了
分布來說明
1.addItemDecoration(this)
這個(gè)方法其實(shí)是調(diào)用了ItemDecoration的接口.從ItemTouchHelper方法聲明部分也可以看到
public class ItemTouchHelper extends RecyclerView.ItemDecoration
implements RecyclerView.OnChildAttachStateChangeListener {}
在ItemTouchHelper中重寫了ItemDecoration接口的兩個(gè)方法如下
@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);
}
從方法可以看到這里其實(shí)沒有做什么特別的工作,只是回調(diào)了Callback的兩個(gè)回調(diào)方法onDrawOver()與onDraw()而這兩個(gè)方法是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();
}
}
這里牽扯的東西比較多,暫時(shí)不分析,我們可以看到有兩個(gè)方法分別為onChildDrawOver()與onChildDraw()
調(diào)用了這兩個(gè)方法,接下來看下這兩個(gè)方法里面有什么?
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);
}
這里面其實(shí)是sUICallback的回調(diào)方法
這里的sUICallback是一個(gè)接口,根據(jù)不同的版本執(zhí)行不同的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()方法來實(shí)現(xiàn)自定義的拖動(dòng)與滑動(dòng)交互
2.addOnChildAttachStateChangeListener(this)
這里調(diào)用了OnChildAttachStateChangeListener這個(gè)接口,這個(gè)接口里有兩個(gè)方法,分別是在RecycleView添加一個(gè)View與刪除一個(gè)View的時(shí)候回調(diào)
那看看我們?cè)贗temTouchHelper中重寫的方法
@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);
}
}
}
因?yàn)镮temTouchHelper中只用考慮移除的情況,
這里面的方法暫時(shí)不介紹,可以看到回調(diào)了clearView()的方法,所以在元素的用戶交互已經(jīng)結(jié)束的時(shí)候,可以通過這個(gè)方法監(jiān)聽到
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調(diào)用了hasDragFlag這個(gè)方法,那我們看下這個(gè)方法
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));
}
經(jīng)過調(diào)用發(fā)現(xiàn)最后調(diào)用了getMovementFlags這個(gè)方法,所以我們重寫方法如果是沒有聲明的在onLongPress中就直接return了,不會(huì)觸發(fā)下面的方法了
再往下看,會(huì)調(diào)用Callback的isLongPressDragEnabled()方法,當(dāng)return為true的時(shí)候會(huì)執(zhí)行select()方法
4. addOnItemTouchListener
這里則是調(diào)用了RecycleView的addOnItemTouchListener方法,ItemTouchHelper重寫了OnItemTouchListener接口的方法,OnItemTouchListener有三個(gè)方法,我們一個(gè)個(gè)來進(jìn)行分析
(1).onInterceptTouchEvent
public boolean onInterceptTouchEvent(RecyclerView recyclerView, MotionEvent event) {
//給前面注冊(cè)的GestureDetector添加監(jiān)聽
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) {
//獲取這個(gè)事件對(duì)應(yīng)的pointerId,ViewDragerHelper中也有說明
mActivePointerId = MotionEventCompat.getPointerId(event, 0);
mInitialTouchX = event.getX();
mInitialTouchY = event.getY();
//初始化跟蹤觸摸屏類VelocityTracker
obtainVelocityTracker();
if (mSelected == null) {
//根據(jù)當(dāng)前的MotionEvent查找RecoverAnimation對(duì)象
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 表示最少有一個(gè)觸控點(diǎn)存在
if (index >= 0) {
checkSelectForSwipe(action, event, index);
}
}
if (mVelocityTracker != null) {
mVelocityTracker.addMovement(event);
}
return mSelected != null;
}
大部分注釋都已經(jīng)說明了,主要把他們調(diào)用的方法來進(jìn)行說明
首先來看下RecoverAnimation這個(gè)類,這個(gè)類中有ValueAnimatorCompat主要是根據(jù)起始點(diǎn)及ActionState等做動(dòng)畫的。
接下來就貼出這個(gè)類
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) {
}
}
里面的成員變量大家看名稱應(yīng)該大部分就可以理解了,不做更多的說明了
那么來看看findAnimation()這個(gè)方法
//根據(jù)查找的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;
}
//根據(jù)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這個(gè)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);
}
//計(jì)算是否點(diǎn)擊在當(dāng)前View的區(qū)域內(nèi)
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方法,這個(gè)方法主要就是從mRecoverAnimations集合中刪除某個(gè)RecoverAnimation對(duì)象
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()方法,這個(gè)方法作用是開始拖動(dòng)或者滑動(dòng)指定的View
private void select(ViewHolder selected, int actionState) {
//當(dāng)ViewHolder一致且State狀態(tài)一致時(shí)候直接返回,不做處理
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;
//當(dāng)mSelected不為null的時(shí)候,新建RecoverAnimation對(duì)象,并且start這個(gè)ValueAnimator
if (mSelected != null) {
final ViewHolder prevSelected = mSelected;
if (prevSelected.itemView.getParent() != null) {
//當(dāng)為DRAG狀態(tài)時(shí)候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;
}
//動(dòng)畫結(jié)束如果swipeDir<=0則drag與swipe失敗,Callback會(huì)調(diào)用clearView方法
//swipeDir >0則表示成功,會(huì)調(diào)用postDispatchSwipe方法
//當(dāng)為DRAG狀態(tài)時(shí)候因?yàn)閟wipeDir為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,我們可以通過重寫這個(gè)方法來設(shè)定動(dòng)畫的時(shí)間
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;
}
//當(dāng)傳進(jìn)來的selected不為空的時(shí)候?qū)elected賦給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會(huì)帶來拖動(dòng)或者滑動(dòng)的ViewHolder改變,所以這里會(huì)調(diào)用onSelectedChanged方法,我們可以通過回調(diào)接受到這些信息
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()) {
//當(dāng)滑動(dòng)結(jié)束會(huì)調(diào)用onSwiped()方法,我們可以在
mCallback.onSwiped(anim.mViewHolder, swipeDir);
} else {
mRecyclerView.post(this);
}
}
}
});
}
通過代碼這部分代碼我們可以大致知道了select這個(gè)方法的作用了,它主要是處理當(dāng)手指拖動(dòng)或者滑動(dòng)結(jié)束后的動(dòng)畫,要通過兩次調(diào)用,第一次在我們選中的時(shí)候,作用是確定我們手指選擇的View,第二次在我們手指放開的時(shí)候,作用是給這個(gè)View設(shè)置動(dòng)畫,并且執(zhí)行。
接下來看看checkSelectForSwipe() 方法
private boolean checkSelectForSwipe(int action, MotionEvent motionEvent, int pointerIndex) {
//這里調(diào)用了Callback的isItemViewSwipeEnabled()方法,我們通過重寫這個(gè)方法可以控制是否可以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;
}
這個(gè)方法主要是確定當(dāng)前的我們選擇的View是否可以滑動(dòng),而前面說的select方法的第一次調(diào)用是在這里,這個(gè)主要是滑動(dòng)的select第一個(gè)方法,拖動(dòng)的第一個(gè)select方法則在GestureDetector的onLongPress中
到這里基本就介紹結(jié)束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,觸發(fā)動(dòng)畫
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時(shí)候觸發(fā)的第二次select()則會(huì)執(zhí)行動(dòng)畫效果
而ACTION_MOVE則是處理隨著手指運(yùn)動(dòng)的效果,那我們看下里面的實(shí)現(xiàn)方法
主要有兩個(gè)一個(gè)是moveIfNecessary另外一個(gè)是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其實(shí)上面的注釋解釋的很清楚,它的作用是檢測(cè)我們滑動(dòng)是否到達(dá)RecycleView的邊緣區(qū)域,如果到達(dá)邊緣區(qū)域則將RecycleView移動(dòng)(scrollBy),這里也調(diào)用了callback的interpolateOutOfBoundsScroll方法,所以我們可以在這里監(jiān)聽到我們拖出視圖邊界的調(diào)用
接下來看一下moveIfNecessary()方法
private void moveIfNecessary(ViewHolder viewHolder) {
if (mRecyclerView.isLayoutRequested()) {
return;
}
if (mActionState != ACTION_STATE_DRAG) {
return;
}
//獲取getMoveThreshold,可以通過重寫來自定義用戶視為拖動(dòng)的距離
final float threshold = mCallback.getMoveThreshold(viewHolder);
final int x = (int) (mSelectedStartX + mDx);
final int y = (int) (mSelectedStartY + mDy);
//當(dāng)移動(dòng)距離小于拖動(dòng)距離,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();
//調(diào)用onMove,可以重寫來讓RecycleView改變,也可以讓callback的onMoved方法是否重調(diào)
if (mCallback.onMove(mRecyclerView, viewHolder, target)) {
// keep target visible
mCallback.onMoved(mRecyclerView, viewHolder, fromPosition,
target, toPosition, x, y);
}
}
所以這里其實(shí)是用來判斷是否move的帶來RecycleView的變化
最后 調(diào)用了mRecyclerView.invalidate()方法,而這個(gè)方法呢則調(diào)用了前面所提到的ItemDecoration里的方法,而那里面的方法處理的是當(dāng)運(yùn)動(dòng)帶來的拖動(dòng)與滑動(dòng)的交互,前面有提到就不做過多的介紹了
所以總結(jié)一下,拖動(dòng)時(shí)通過mRecyclerView.invalidate讓onDraw不斷重繪帶來手指與點(diǎn)擊ViewHolder的變化,當(dāng)手指離開時(shí)候則通過select方法啟動(dòng)RecoverAnimation讓ViewHolder執(zhí)行后面的動(dòng)畫,基本的流程就是這樣,當(dāng)然中間有很多Callback方法帶來不同的變化,感覺整個(gè)流程我還是講的比較亂的,大家理解大致流程就好了。。到這里就基本分析結(jié)束了,當(dāng)然還有一些方法可能沒有介紹了,大家可以通過前面的Callback注釋的方法去理解大致的功能就好了。