源码分析 ItemTouchHelper手势的入口 (OnInterceptTouchEvent onLongPress等)
来源:互联网 发布:windows控制mac 编辑:程序博客网 时间:2024/06/05 05:29
ItemTouchHelper是v7包RecyclerView的ItemDecoration接口的一个实现,其前身是v4包的ViewDragHelper(可在任意ViewGroup中使用)在DrawerLayout、SlidingPaneLayout源码中都有应用。
两者使用方法也很类似,都是实现各自的Callback
ItemTouchHelper手势的实现都在匿名内部类mOnItemTouchListener中实现,
private final OnItemTouchListener mOnItemTouchListener = new OnItemTouchListener()
与RecyclerView的绑定,则在attachToRecyclerView方法中的setupCallbacks()中
add到RV中
/** * Attaches the ItemTouchHelper to the provided RecyclerView. If TouchHelper is already * attached to a RecyclerView, it will first detach from the previous one. You can call this * method with {@code null} to detach it from the current RecyclerView. * * @param recyclerView The RecyclerView instance to which you want to add this helper or * {@code null} if you want to remove ItemTouchHelper from the current * RecyclerView. */ 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(); //初始化 setup } } private void setupCallbacks() { ViewConfiguration vc = ViewConfiguration.get(mRecyclerView.getContext()); mSlop = vc.getScaledTouchSlop(); //初始化TouchSlop mRecyclerView.addItemDecoration(this); //绑定Callback.OnDraw供实现onChildDraw mRecyclerView.addOnItemTouchListener(mOnItemTouchListener); //绑定手势监听 mRecyclerView.addOnChildAttachStateChangeListener(this); initGestureDetector(); } 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(); }
进入正题
严格的说,ItemTouchHelper的手势正式接管方法是select()方法,而通过手势开始(达到某种条件)而进入select的入口总共3个
1、长按
2、与rv垂直方向的swipe移动(超过touchSlop的Move)
3、按下一个正在做恢复动画的vh(对RecoverAnimation没做完的vh触发Down事件)
既然是手势接管,毫无疑问,这些入口的入口一定来自mOnItemTouchListener的OnInterceptTouchEvent方法
@Override public boolean onInterceptTouchEvent(RecyclerView recyclerView, MotionEvent event) { mGestureDetector.onTouchEvent(event);//主要实现onLongPress的监听这里就是入口1,下有对于ItemTouchHelperGestureListener的onLongPress具体分析
//如果是个长按 那么拦截短路掉RV自己的滑动 //并判定Down选中的vh ItemTouchHelper正式接管 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 = event.getPointerId(0);下面记录down的位置,注意这里是DOWN的时候无条件记录!!!
因为接管手势是未来的事,之后的位移比对等等都来自这个初始记录值
长按的时候这个初始值还会被替换,(因为长按的起始是长按600ms而不是DOWN)
如果这里代码没有被执行 startDrag startSwipe等方法在外部调用是无法被正确工作的!
startDrag等方法工作前提的说明见我的另一篇点击打开链接
mInitialTouchX = event.getX(); mInitialTouchY = event.getY(); obtainVelocityTracker(); if (mSelected == null) { final RecoverAnimation animation = findAnimation(event);下面是入口3,一个并不常见的场景
if (animation != null) { //特别注意!!! 这里并不是一个主流程的手势接管入口 //该段代码仅处理DOWN按下一个正在做恢复动画的vh的场景! //千万不要误解,通常DOWN的时候是没有恢复动画的,所以mSelected不会被赋值 //intercept也就不会成功,后面的手势OnItemTouchListener会正常执行onIntercepter方法 //若都返回false 也会传递到vh的itemView的DispatchTouchEvent里 mInitialTouchX -= animation.mX; mInitialTouchY -= animation.mY; endRecoverAnimation(animation.mViewHolder, true); //肯定把正在进行的恢复动画先干掉咯 if (mPendingCleanup.remove(animation.mViewHolder.itemView)) { //mPendingCleanup 存储动画之后就应该detach并被cleanup的Views mCallback.clearView(mRecyclerView, animation.mViewHolder); //如果正在被移除 恢复vh } select(animation.mViewHolder, animation.mActionState); //调用select 接管! updateDxDy(event, mSelectedFlags, 0); } } }
处理cancel与抬起
else if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) { mActivePointerId = ACTIVE_POINTER_ID_NONE; //如果是cancel或up 那么select(null) select(null, ACTION_STATE_IDLE); }再下面就是Swipe的入口了 入口2
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 = event.findPointerIndex(mActivePointerId); if (DEBUG) { Log.d(TAG, "pointer index " + index); } if (index >= 0) { //检验PointerId合法性 不是ACTIVE_POINTER_ID_NONE -1 checkSelectForSwipe(action, event, index); //Swipe的入口 //前提:(没有接管过mSelect为空、必须是Move事件、支持swipe、拖动超过Slop且与Rv方向垂直) } } if (mVelocityTracker != null) { mVelocityTracker.addMovement(event); } return mSelected != null; //特别特别注意: mSelected是否为空决定是否接管!!!(select方法是否调用) }
onIntercepterTouchEvent 到此结束 注意它的返回值
下面来看一下入口1和入口2的具体实现
长按手势的入口ItemTouchHelperGestureListener, 也就是GestureDetector的回调实现。
private class ItemTouchHelperGestureListener extends GestureDetector.SimpleOnGestureListener { ItemTouchHelperGestureListener() { } @Override public boolean onDown(MotionEvent e) { return true; } @Override public void onLongPress(MotionEvent e) { View child = findChildView(e);//先找点了谁(vh) 点到花花草草(divider分割线之类)可不作数哦 if (child != null) { ViewHolder vh = mRecyclerView.getChildViewHolder(child); if (vh != null) {//注意 这个hasDragFlag方法和hasSwipeFlag一样调用public可重写的getAbsoluteMovementFlags方法//入参有vh 也就是说可以像checkSelectForSwipe定制swipe方向一样//定制各个vh自己的move方向 if (!mCallback.hasDragFlag(mRecyclerView, vh)) { return; } int pointerId = e.getPointerId(0); // Long press is deferred. // Check w/ active pointer id to avoid selecting after motion // event is canceled. if (pointerId == mActivePointerId) { final int index = e.findPointerIndex(mActivePointerId); final float x = e.getX(index); final float y = e.getY(index); mInitialTouchX = x; //如果长按了 那么长按才是手势的正式入口 mInitialTouchY = y; mDx = mDy = 0f; if (DEBUG) { Log.d(TAG, "onlong press: x:" + mInitialTouchX + ",y:" + mInitialTouchY); } if (mCallback.isLongPressDragEnabled()) {//最关键的校验 如果你通过重写这方法 //禁用了LongPressDragEnabled HOHO 那它肯定也就不拖动了 select(vh, ACTION_STATE_DRAG); } } } } } }接下来看Move事件Swipe的入口
/** * Checks whether we should select a View for swiping. */ boolean checkSelectForSwipe(int action, MotionEvent motionEvent, int pointerIndex) { 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);//检验手势目前是否符合Swipe 下有分析 if (vh == null) { return false; }//之前校验了一堆 如果已经有mSelected 那么不用再校验了//校验只检验Move事件 且itemTouchHelper必须是拖动状态//Callback必须支持Swipe isItemViewSwipeEnabled没有被重写flase//如果RecyclerView已经处理了拖动状态————对不起也不行//如果点击的地方没有viewHoler不行(比如点到了divider?) final int movementFlags = mCallback.getAbsoluteMovementFlags(mRecyclerView, vh);//获取支持的swipe方向,注意!这里getAbsoluteMovementFlags入参有vh,也就是说你//可以定制每一个vh的swipe或者move方向(见OnLongPress) 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 = motionEvent.getX(pointerIndex); final float y = motionEvent.getY(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 = motionEvent.getPointerId(0); select(vh, ACTION_STATE_SWIPE); return true; }检验这个手势是否是swipe一个ViewHolder
private ViewHolder findSwipedView(MotionEvent motionEvent) { final RecyclerView.LayoutManager lm = mRecyclerView.getLayoutManager(); if (mActivePointerId == ACTIVE_POINTER_ID_NONE) { //再次校验pointerId是否有效 return null; } final int pointerIndex = motionEvent.findPointerIndex(mActivePointerId); final float dx = motionEvent.getX(pointerIndex) - mInitialTouchX; final float dy = motionEvent.getY(pointerIndex) - mInitialTouchY; final float absDx = Math.abs(dx); final float absDy = Math.abs(dy); if (absDx < mSlop && absDy < mSlop) { //判断是否超过TouchSlop return null; } if (absDx > absDy && lm.canScrollHorizontally()) { //如果RecyclerView滑动的同一个方向 //的位移更大,那么这应该交给RV处理,这里不是个swipe return null; } else if (absDy > absDx && lm.canScrollVertically()) { return null; } View child = findChildView(motionEvent); if (child == null) { return null; } return mRecyclerView.getChildViewHolder(child); }下面分析一下最重要的接管方法select()
/** * Starts dragging or swiping the given View. Call with null if you want to clear it. * * @param selected The ViewHolder to drag or swipe. Can be null if you want to cancel the * current action * @param actionState The type of action */ void select(ViewHolder selected, int actionState) { 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) { //如果原来有接管目标vh的 那么那个vh要寿终正寝 完成后事动画 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: 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) { //如果原来有接管对象vh的 animationType = ANIMATION_TYPE_DRAG; //拖动的变状态为拖动动画 } else if (swipeDir > 0) { animationType = ANIMATION_TYPE_SWIPE_SUCCESS; //swipe的变状态为swipe动画 } 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; } if (swipeDir <= 0) { // this is a drag or failed swipe. recover immediately //如果是个拖拉(onMove)或者没有过阈值的swipe 是没有动画的 //立即恢复 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. //动画可能在布局过程中由其他animators完成。//所以这里用递归判断isRunning!!!,以避免在布局期间编辑适配器。//保证onSwipe的回调(特别注意:动画结束不意味着mRecoverAnimations里//对应的vh被清除! 依赖于onSwipe里移除对应数据而移除vh之后执行//onChildViewDetachedFromWindow的回调里endRecoverAnimation()移除) 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); //注意!!! 这个CallBack的getAnimationDuration方法可以重写 //自定义动画时间 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) { //如果select有值 那么Disallow拦截 这个号理解 rvParent.requestDisallowInterceptTouchEvent(mSelected != null); } if (!preventLayout) { mRecyclerView.getLayoutManager().requestSimpleAnimationsInNextLayout(); } mCallback.onSelectedChanged(mSelected, mActionState); //注意 这里接管和放开接管都有CallBack回调 //可以重写的!!! mRecyclerView.invalidate(); }
阅读全文
0 0
- 源码分析 ItemTouchHelper手势的入口 (OnInterceptTouchEvent onLongPress等)
- ItemTouchHelper源码分析 手势分析OnTouchEvent
- ItemTouchHelper源码分析(上)
- ItemTouchHelper源码分析(中)
- RecyclerView ItemTouchHelper源码分析扩展
- ItemTouchHelper源码分析 拖拽到屏幕边缘的处理
- gallery3d的源码分析——入口
- Appium源码分析(1)-程序的入口
- (二十八)RecyclerView ItemTouchHelper 源码分析以及拓展
- ViewGroup的onInterceptTouchEvent()事件分析
- ItemTouchHelper源码解析
- jQuery Sizzle 入口 [ 源码分析 ]
- 第二人生的源码分析(3)程序入口点
- 第二人生的源码分析(3)程序入口点
- gallery3d的源码分析——入口2
- [java][junit4][源码分析]JUnitCore-入口分析
- Mongodb源码分析--主程序入口main()
- Mongodb源码分析--主程序入口main()
- 神舟I号可能遇到的问题及解…
- C++的运算符重载
- 大学生,其实我们可以晚点再恋爱【…
- Codeforces Round #142 (Div. 2) C. Shifts
- DHT11数字温湿度传感器实验【转】
- 源码分析 ItemTouchHelper手势的入口 (OnInterceptTouchEvent onLongPress等)
- FAT16文件系统
- STC12C5A60S2单片机IO口工作…
- AVR笔记8:mega16再次锁死
- 关于AVR锁死的解决办法
- 从UCOS到多任务实现--初学者…
- Ubuntu更改用户名及相应的用户主目…
- IBM刀片登陆方式
- ubuntu 如何修改当前用户名【转】