【Android 事件分发】ItemTouchHelper 源码分析 ( OnItemTouchListener 事件监听器源码分析 )
Android 事件分發 系列文章目錄
【Android 事件分發】事件分發源碼分析 ( 驅動層通過中斷傳遞事件 | WindowManagerService 向 View 層傳遞事件 )
【Android 事件分發】事件分發源碼分析 ( Activity 中各層級的事件傳遞 | Activity -> PhoneWindow -> DecorView -> ViewGroup )
【Android 事件分發】事件分發源碼分析 ( ViewGroup 事件傳遞機制 一 )
【Android 事件分發】事件分發源碼分析 ( ViewGroup 事件傳遞機制 二 )
【Android 事件分發】事件分發源碼分析 ( ViewGroup 事件傳遞機制 三 )
【Android 事件分發】事件分發源碼分析 ( ViewGroup 事件傳遞機制 四 | View 事件傳遞機制 )
【Android 事件分發】事件分發源碼分析 ( ViewGroup 事件傳遞機制 五 )
【Android 事件分發】事件分發源碼分析 ( ViewGroup 事件傳遞機制 六 )
【Android 事件分發】事件分發源碼分析 ( ViewGroup 事件傳遞機制 七 )
【Android 事件分發】ItemTouchHelper 簡介 ( 拖動/滑動事件 | ItemTouchHelper.Callback 回調 )
【Android 事件分發】ItemTouchHelper 實現側滑刪除 ( 設置滑動方向 | 啟用滑動操作 | 滑動距離判定 | 滑動速度判定 | 設置動畫時間 | 設置側滑觸發操作 )
【Android 事件分發】ItemTouchHelper 實現拖動排序 ( 設置滑動方向 | 啟啟用長按拖動功能 | 拖動距離判定 | 設置拖動觸發操作 )
【Android 事件分發】ItemTouchHelper 事件分發源碼分析 ( 綁定 RecyclerView )
【Android 事件分發】ItemTouchHelper 源碼分析 ( OnItemTouchListener 事件監聽器源碼分析 )
文章目錄
- Android 事件分發 系列文章目錄
- 一、OnItemTouchListener 事件監聽器引入
- 二、OnItemTouchListener 觸摸事件攔截方法 onInterceptTouchEvent
- 1、onInterceptTouchEvent 方法簡介
- 2、處理按下事件
- 3、findAnimation 方法
- 4、findChildView 方法
- 5、動作取消
- 6、動作完成
- 三、ItemTouchHelper 涉及到的本博客相關源碼
- 四、博客資源
一、OnItemTouchListener 事件監聽器引入
在上一篇博客 【Android 事件分發】ItemTouchHelper 事件分發源碼分析 ( 綁定 RecyclerView ) 分析了 ItemTouchHelper 添加時 , 調用了 mItemTouchHelper.attachToRecyclerView(recycler_view) 方法 , 將 ItemTouchHelper 與 RecyclerView 進行關聯 , 并在 attachToRecyclerView 方法的最后 , 調用了 setupCallbacks 方法 ;
// 該方法與 destroyCallbacks 方法相對應private void setupCallbacks() {// 配置相關 ViewConfiguration vc = ViewConfiguration.get(mRecyclerView.getContext());mSlop = vc.getScaledTouchSlop();// 設置 RecyclerView 條目中的裝飾 , 可以在條目組件 底部 上層 繪制 Canvas 圖形 // ItemTouchHelper 繼承 RecyclerView.ItemDecorationmRecyclerView.addItemDecoration(this);// 添加了每個條目上的觸摸監聽器 mOnItemTouchListener // 該監聽器是定義在 ItemTouchHelper 中的成員變量 mRecyclerView.addOnItemTouchListener(mOnItemTouchListener);mRecyclerView.addOnChildAttachStateChangeListener(this);startGestureDetection();}上一篇博客分析到 mRecyclerView.addItemDecoration(this) 位置 , 本篇博客重點分析 mRecyclerView.addOnItemTouchListener(mOnItemTouchListener) 中的 mOnItemTouchListener , 這是 RecyclerView 的 ItemTouchHelper 的核心 ;
OnItemTouchListener 是 RecyclerView 中定義的作用與條目組件的觸摸監聽器 , 主要是攔截觸摸事件方法 onInterceptTouchEvent 和 消費觸摸事件方法 onTouchEvent ;
public class RecyclerView extends ViewGroup implements ScrollingView,NestedScrollingChild2, NestedScrollingChild3 {public interface OnItemTouchListener {boolean onInterceptTouchEvent(@NonNull RecyclerView rv, @NonNull MotionEvent e);void onTouchEvent(@NonNull RecyclerView rv, @NonNull MotionEvent e);void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept);} }ItemTouchHelper 事件分發 , 分析手指觸摸的 按下 , 移動 , 抬起 事件 ;
在 OnItemTouchListener 觸摸監聽器中 , onInterceptTouchEvent 方法處理的是事件攔截機制 , onTouchEvent 方法是最終消費事件的方法 ;
在 onInterceptTouchEvent 事件攔截 中 , 只攔截 MotionEvent.ACTION_DOWN / MotionEvent.ACTION_CANCEL / MotionEvent.ACTION_UP 等 333 種事件 , 不攔截 MotionEvent.ACTION_MOVE 事件 ;
在 onTouchEvent 事件消費 中 , 才處理 MotionEvent.ACTION_MOVE 事件 ;
ItemTouchHelper 沒有對子控件進行事件分發 , 其只識別一層組件 , 如果內部有多層組件 , 內層的組件無法分發到事件 ;
二、OnItemTouchListener 觸摸事件攔截方法 onInterceptTouchEvent
1、onInterceptTouchEvent 方法簡介
在 ItemTouchHelper 中定義的成員變量
private final OnItemTouchListener mOnItemTouchListener = new OnItemTouchListener() {// 攔截觸摸事件 , 處理攔截機制 @Overridepublic boolean onInterceptTouchEvent(@NonNull RecyclerView recyclerView,@NonNull MotionEvent event) {} }中實現的 RecyclerView.OnItemTouchListener 接口的 onInterceptTouchEvent 方法 , 主要是用于作用與條目上的觸摸事件的攔截 ;
注意此處攔截的動作 , 只攔截 DOWN / UP / CANCEL 三種動作 , MOVE 動作不攔截 , 取消操作很少遇到 , 因此 , 攔截機制中 , 只負責攔截手指按下 和 抬起 操作 , 在 ItemTouchHelper 的業務邏輯中 , 不需要處理移動事件 ;
2、處理按下事件
當檢測到 MotionEvent.ACTION_DOWN 按下操作時 , 獲取按下的 XY 坐標 , 并進行滑動速度檢測 ;
// 注意此處攔截的動作 , 只攔截 DOWN / UP / CANCEL 三種動作 , MOVE 動作不攔截 // 取消操作很少遇到 // 因此 , 攔截機制中 , 只負責攔截手指按下 和 抬起 操作 // 在 ItemTouchHelper 的業務邏輯中 , 不需要處理移動事件 if (action == MotionEvent.ACTION_DOWN) {// 按下操作 , 得到初始 XY 坐標位置 mActivePointerId = event.getPointerId(0);mInitialTouchX = event.getX();mInitialTouchY = event.getY();// 滑動速度檢測 obtainVelocityTracker();ViewHolder mSelected = null; 成員是當前正在點擊的 ViewHolder 條目 , 如果該 mSelected 成員為空 , 則執行后續操作 ;
// mSelected 是當前正在點擊的條目的 ViewHolder if (mSelected == null) {// 恢復動畫 , 查找手指按下的 View 子組件 , 該子組件時 RecyclerView 中的一個條目 // 用戶按下 RecyclerView 中的某個條目 // findAnimation 方法用于找到按下的條目 View , 并設置給 RecoverAnimation 恢復動畫 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);}// 為動畫選擇 item 項 select(animation.mViewHolder, animation.mActionState);// 計算當前移動的位置 , 初始位置 與 最后一次事件的位置 偏移值 . updateDxDy(event, mSelectedFlags, 0);}}先恢復動畫 , 查找手指按下的 View 子組件 , 該子組件是 RecyclerView 中的一個條目 , 用戶按下 RecyclerView 中的某個條目 , findAnimation 方法用于找到按下的條目 View , 并設置給 RecoverAnimation 恢復動畫 ;
3、findAnimation 方法
在 findAnimation 方法中 , 先調用了 findChildView 方法 , 查找手指按下的 View 子組件 , 該子組件是 RecyclerView 中的一個條目 ;
// 找到手指按下所在位置的條目的 View 組件// 查找手指按下的 View 子組件 , 該子組件時 RecyclerView 中的一個條目 View target = findChildView(event);找到該條目對應的 View 組件后 , 遍歷恢復動畫 , 動畫中有 mViewHolder 成員 , mViewHolder 中有 itemView 成員 , 設置 anim.mViewHolder.itemView 為手指按下的子組件 , 即設置該動畫作用于 RecyclerView 的哪個條目上 ;
for (int i = mRecoverAnimations.size() - 1; i >= 0; i--) {final RecoverAnimation anim = mRecoverAnimations.get(i);if (anim.mViewHolder.itemView == target) {return anim;}}findAnimation 方法完整代碼注釋 :
// 該方法作用 : // 用戶按下 RecyclerView 中的某個條目 // 該方法用于找到按下的條目 View , 并設置給 RecoverAnimation 恢復動畫 @SuppressWarnings("WeakerAccess") /* synthetic access */RecoverAnimation findAnimation(MotionEvent event) {if (mRecoverAnimations.isEmpty()) {return null;}// 找到手指按下所在位置的條目的 View 組件// 查找手指按下的 View 子組件 , 該子組件時 RecyclerView 中的一個條目 View target = findChildView(event);// 遍歷恢復動畫 // 動畫中有 mViewHolder 成員 , mViewHolder 中有 itemView 成員 // 設置 anim.mViewHolder.itemView 為手指按下的子組件 // 即設置該動畫作用于 RecyclerView 的哪個條目上 ; for (int i = mRecoverAnimations.size() - 1; i >= 0; i--) {final RecoverAnimation anim = mRecoverAnimations.get(i);if (anim.mViewHolder.itemView == target) {return anim;}}return null;}4、findChildView 方法
根據按下的 X, Y 坐標 , 查找對應的條目組件 , 先獲取觸摸的 XY 坐標 ;
final float x = event.getX();final float y = event.getY();如果 mSelected 成員不為空 , 則直接使用 , 分支中直接返回了 ;
if (mSelected != null) {final View selectedView = mSelected.itemView;if (hitTest(selectedView, x, y, mSelectedStartX + mDx, mSelectedStartY + mDy)) {return selectedView;}}如果 mSelected 為空 , 則開始遍歷進行檢測 ;
for (int i = mRecoverAnimations.size() - 1; i >= 0; i--) {final RecoverAnimation anim = mRecoverAnimations.get(i);final View view = anim.mViewHolder.itemView;// 根據當前按下的坐標 , 找到列表條目對應的 View 組件 if (hitTest(view, x, y, anim.mX, anim.mY)) {return view;}}findChildView 完整代碼注釋 :
@SuppressWarnings("WeakerAccess") /* synthetic access */View findChildView(MotionEvent event) {// first check elevated views, if none, then call RV// 根據按下的 X, Y 坐標 , 查找對應的條目 final float x = event.getX();final float y = event.getY();// 如果 mSelected 成員不為空 , 則直接使用 , 分支中直接返回了 if (mSelected != null) {final View selectedView = mSelected.itemView;if (hitTest(selectedView, x, y, mSelectedStartX + mDx, mSelectedStartY + mDy)) {return selectedView;}}// 如果 mSelected 為空 , 則開始遍歷進行檢測 for (int i = mRecoverAnimations.size() - 1; i >= 0; i--) {final RecoverAnimation anim = mRecoverAnimations.get(i);final View view = anim.mViewHolder.itemView;// 根據當前按下的坐標 , 找到列表條目對應的 View 組件 if (hitTest(view, x, y, anim.mX, anim.mY)) {return view;}}return mRecyclerView.findChildViewUnder(x, y);}5、動作取消
當攔截的動作是 MotionEvent.ACTION_CANCEL 或 action == MotionEvent.ACTION_UP 動作時 , 說明用戶取消了該動作 , 將選擇的組件置空 ;
} else if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {mActivePointerId = ACTIVE_POINTER_ID_NONE;// 抬起 / 取消 時 , 選擇項 置空 . select(null, ACTION_STATE_IDLE);}6、動作完成
ACTIVE_POINTER_ID_NONE 表示是否完成了滑動 , 如果滑動完成 , 觸發了側滑事件 , 才會進入該 else if (mActivePointerId != ACTIVE_POINTER_ID_NONE) 分支 , 如果滑動沒有完成 , 滑到半路 , 松開手 , 條目組件縮回去了 , 則不會進入該分支 ;
} else if (mActivePointerId != ACTIVE_POINTER_ID_NONE) {// 該分支表示滑動操作完成的分支 // ACTIVE_POINTER_ID_NONE 表示是否完成了滑動 // 如果滑動完成 , 觸發了側滑事件 , 才會進入該分支 // 如果滑動沒有完成 , 滑到半路 , 松開手 , 條目組件縮回去了 , 則不會進入該分支 // in a non scroll orientation, if distance change is above threshold, we// can select the item// 滑動完成后 , 記錄當前的觸摸指針索引final int index = event.findPointerIndex(mActivePointerId);if (DEBUG) {Log.d(TAG, "pointer index " + index);}if (index >= 0) {// 檢查是否完成了滑動操作 checkSelectForSwipe(action, event, index);}}三、ItemTouchHelper 涉及到的本博客相關源碼
public class ItemTouchHelper extends RecyclerView.ItemDecorationimplements RecyclerView.OnChildAttachStateChangeListener {/*** Currently selected view holder*/@SuppressWarnings("WeakerAccess") /* synthetic access */ViewHolder mSelected = null;/*** The diff between the last event and initial touch.* 最后的觸摸事件和初始觸摸事件之間的坐標差異 , 偏移值 .*/float mDx;float mDy;private final OnItemTouchListener mOnItemTouchListener = new OnItemTouchListener() {// 攔截觸摸事件 , 處理攔截機制 @Overridepublic boolean onInterceptTouchEvent(@NonNull RecyclerView recyclerView,@NonNull MotionEvent event) {mGestureDetector.onTouchEvent(event);if (DEBUG) {Log.d(TAG, "intercept: x:" + event.getX() + ",y:" + event.getY() + ", " + event);}final int action = event.getActionMasked();// 注意此處攔截的動作 , 只攔截 DOWN / UP / CANCEL 三種動作 , MOVE 動作不攔截 // 取消操作很少遇到 // 因此 , 攔截機制中 , 只負責攔截手指按下 和 抬起 操作 // 在 ItemTouchHelper 的業務邏輯中 , 不需要處理移動事件 if (action == MotionEvent.ACTION_DOWN) {// 按下操作 , 得到初始 XY 坐標位置 mActivePointerId = event.getPointerId(0);mInitialTouchX = event.getX();mInitialTouchY = event.getY();// 滑動速度檢測 obtainVelocityTracker();// mSelected 是當前正在點擊的條目的 ViewHolder if (mSelected == null) {// 恢復動畫 , 查找手指按下的 View 子組件 , 該子組件時 RecyclerView 中的一個條目 // 用戶按下 RecyclerView 中的某個條目 // findAnimation 方法用于找到按下的條目 View , 并設置給 RecoverAnimation 恢復動畫 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);}// 為動畫選擇 item 項 select(animation.mViewHolder, animation.mActionState);// 計算當前移動的位置 , 初始位置 與 最后一次事件的位置 偏移值 . updateDxDy(event, mSelectedFlags, 0);}}} else if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {mActivePointerId = ACTIVE_POINTER_ID_NONE;// 抬起 / 取消 時 , 選擇項 置空 . select(null, ACTION_STATE_IDLE);} else if (mActivePointerId != ACTIVE_POINTER_ID_NONE) {// 該分支表示滑動操作完成的分支 // ACTIVE_POINTER_ID_NONE 表示是否完成了滑動 // 如果滑動完成 , 觸發了側滑事件 , 才會進入該分支 // 如果滑動沒有完成 , 滑到半路 , 松開手 , 條目組件縮回去了 , 則不會進入該分支 // in a non scroll orientation, if distance change is above threshold, we// can select the item// 滑動完成后 , 記錄當前的觸摸指針索引final int index = event.findPointerIndex(mActivePointerId);if (DEBUG) {Log.d(TAG, "pointer index " + index);}if (index >= 0) {// 檢查是否完成了滑動操作 checkSelectForSwipe(action, event, index);}}if (mVelocityTracker != null) {mVelocityTracker.addMovement(event);}return mSelected != null;}};// 該方法作用 : // 用戶按下 RecyclerView 中的某個條目 // 該方法用于找到按下的條目 View , 并設置給 RecoverAnimation 恢復動畫 @SuppressWarnings("WeakerAccess") /* synthetic access */RecoverAnimation findAnimation(MotionEvent event) {if (mRecoverAnimations.isEmpty()) {return null;}// 找到手指按下所在位置的條目的 View 組件// 查找手指按下的 View 子組件 , 該子組件時 RecyclerView 中的一個條目 View target = findChildView(event);// 遍歷恢復動畫 // 動畫中有 mViewHolder 成員 , mViewHolder 中有 itemView 成員 // 設置 anim.mViewHolder.itemView 為手指按下的子組件 // 即設置該動畫作用于 RecyclerView 的哪個條目上 ; for (int i = mRecoverAnimations.size() - 1; i >= 0; i--) {final RecoverAnimation anim = mRecoverAnimations.get(i);if (anim.mViewHolder.itemView == target) {return anim;}}return null;}@SuppressWarnings("WeakerAccess") /* synthetic access */View findChildView(MotionEvent event) {// first check elevated views, if none, then call RV// 根據按下的 X, Y 坐標 , 查找對應的條目 final float x = event.getX();final float y = event.getY();// 如果 mSelected 成員不為空 , 則直接使用 , 分支中直接返回了 if (mSelected != null) {final View selectedView = mSelected.itemView;if (hitTest(selectedView, x, y, mSelectedStartX + mDx, mSelectedStartY + mDy)) {return selectedView;}}// 如果 mSelected 為空 , 則開始遍歷進行檢測 for (int i = mRecoverAnimations.size() - 1; i >= 0; i--) {final RecoverAnimation anim = mRecoverAnimations.get(i);final View view = anim.mViewHolder.itemView;// 根據當前按下的坐標 , 找到列表條目對應的 View 組件 if (hitTest(view, x, y, anim.mX, anim.mY)) {return view;}}return mRecyclerView.findChildViewUnder(x, y);}// 判斷刪上下左右邊距 是否在對應子組件范圍內 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();}/*** Starts dragging or swiping the given View. Call with null if you want to clear it.* 開始拖動/滑動給定的 View 組件. 如果想要清除傳入 null. * 為動畫選擇 item 項 * 該方法中進行一系列的計算 ** @param selected The ViewHolder to drag or swipe. Can be null if you want to cancel the* current action, but may not be null if actionState is ACTION_STATE_DRAG.* @param actionState The type of action*/@SuppressWarnings("WeakerAccess") /* synthetic access */void select(@Nullable ViewHolder selected, int actionState) {if (selected == mSelected && actionState == mActionState) {return;}mDragScrollStartTimeInMs = Long.MIN_VALUE;final int prevActionState = mActionState;// prevent duplicate animationsendRecoverAnimation(selected, true);mActionState = actionState;if (actionState == ACTION_STATE_DRAG) {if (selected == null) {throw new IllegalArgumentException("Must pass a ViewHolder when dragging");}// 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) {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 tofinal 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) {@Overridepublic void onAnimationEnd(Animator animation) {super.onAnimationEnd(animation);if (this.mOverridden) {return;}if (swipeDir <= 0) {// this is a drag or failed swipe. recover immediatelymCallback.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 timeif (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 = 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();}mCallback.onSelectedChanged(mSelected, mActionState);mRecyclerView.invalidate();}// 計算當前移動的位置 @SuppressWarnings("WeakerAccess") /* synthetic access */void updateDxDy(MotionEvent ev, int directionFlags, int pointerIndex) {final float x = ev.getX(pointerIndex);final float y = ev.getY(pointerIndex);// Calculate the distance movedmDx = 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);}}}
四、博客資源
博客資源 :
- GitHub 地址 : https://github.com/han1202012/001_RecyclerView
總結
以上是生活随笔為你收集整理的【Android 事件分发】ItemTouchHelper 源码分析 ( OnItemTouchListener 事件监听器源码分析 )的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【算法】快速选择算法 ( 数组中找第 K
- 下一篇: 【Android 事件分发】ItemTo