本文转载请联系Android开发编程公众号。前言ViewDragHelper类用于处理View边界的拖动相关类;主要功能是处理View上的触摸事件,记录触摸点,计算距离,滚动动画,状态回调等,如果我们手动实现,自然会很麻烦。可能会有错误,这个类会帮助我们大大简化工作量;今天我们来分析一波;一、ViewDragHelper主要API介绍1、ViewDragHelpercreate(ViewGroupforParent,Callbackcb)是静态创建方法;参数1:入口和出口是对应的ViewGroup;参数2:是回调,需要自己实现;2.shouldInterceptTouchEvent(MotionEventev)处理事件分发(这个方法怎么说呢?主要是分发ViewGroup事件,委托给ViewDragHelper处理);参数1:MotionEventev主要是ViewGroup的事件;3.processTouchEvent(MotionEvent事件)是处理对应TouchEvent的方法。这里,需要注意一个问题。在处理对应的TouchEvent时,返回结果为true,消费该事件。否则将无法使用ViewDragHelper来处理相应的拖动事件;4、ViewDragHelper.Callback的APItryCaptureView(Viewchild,intpointerId)这是一个必须实现的抽象类,下面的方法只有在该方法返回true时才会生效;onViewDragStateChanged(intstate)状态改变时回调,返回对应的状态(这里一共有三种状态);STATE_IDLE空闲状态;STATE_DRAGGING正在拖动;STATE_SETTLING放置在某个位置;onViewPositionChanged(ViewchangedView,intleft,inttop,intdx,intdy)当你拖动的View的位置发生变化时回调;参数1:当前拖动的View参数2:距离左边的距离参数3:距离右边的距离参数4:x轴的变化量参数5:y轴的变化量onViewCaptured(ViewcapturedChild,intactivePointerId)View被捕获时调用的方法。参数1:捕获的View(也就是你拖拽的View);参数2:不知道这个参数是什么意思。API中写的是什么指针?,这里我没有看到也没有注意到;onViewReleased(ViewreleasedChild,floatxvel,floatyvel)View停止拖动时调用的方法参数1:你拖动的View参数2:x轴的速度参数3:y轴的速度clampViewPositionVertical(Viewchild,inttop,intdy)垂直拖动时的回调方法参数一:被拖动的View参数二:距离顶部的距离参数三:变化clampViewPositionHorizo??ntal(Viewchild,intleft,intdx)水平拖动时的回调方法参数一:拖动View参数二:距离左边参数三:变体二、实现原理介绍1、初始化privateViewDragHelper(Contextcontext,ViewGroupforParent,Callbackcb){...mParentView=forParent;//BaseViewmCallback=cb;//callbackfinalViewConfigurationvc=ViewConfiguration.get(context);finalfloatdensity=context.getResources().getDisplayMetrics().density;mEdgeSize=(int)(EDGE_SIZE*density+0.5f);//边界拖动距离范围mTouchSlop=vc.getScaledTouchSlop();//拖动距离thresholdmScroller=newOverScroller(context,sInterpolator);//Scroller}mParentView指的是哪个View是基于触摸处理的;mCallback是触摸处理回调的各个阶段;mEdgeSize指的是边框内距离多少算拖动,默认为20dp;mTouchSlop是指滑动多少距离算作拖动,是系统默认值;mScroller是View滚动的Scroller对象,用于处理触摸释放后的View。滚动行为,如滚动回原位置或滚动出屏幕;2.拦截事件处理该类提供booleanshouldInterceptTouchEvent(MotionEvent)方法:overridefunonInterceptTouchEvent(ev:MotionEvent?)=dragHelper?.shouldInterceptTouchEvent(ev)?:super.onInterceptTouchEvent(ev)该方法用于处理mParentView是否拦截该事件publicbooleanshouldInterceptTouchEvent(MotionEventev){...switch(action){...caseMotionEvent.ACTION_MOVE:{if(mInitialMotionX==null||mInitialMotionY==null)break;//Firsttocrossatouchslopoveradraggableviewwins.Alsoreportedgedrags.finalintpointerCount=ev.getPointerCount();for(inti=0;imTouchSlop;}可以看出ACTION_MOVE时会尝试找到指针对应的拖拽边界,并且这个边界可以由我们自己制定,比如关闭页面的侧滑是从左边开始的,那么我们可以调用setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT)设置只支持左滑一旦发生滚动,回调的onEdgeDragStarted方法就会被回调,我们会做如下操作:),pointerId)}我们调用ViewDragHelper的captureChildView方法:StatetoStartdragging}此时,被拖动的View被记录下来,状态设置为拖动,那么下次ACTION_MOVE时,mParentView会拦截事件,让自己的onTouchEvent方法处理拖动;3.拖动事件处理该类提供了voidprocessTouchEvent(MotionEvent)方法,通常我们需要这样写:overridefunonTouchEvent(event:MotionEvent?):Boolean{dragHelper?.processTouchEvent(event)//handledbyViewDragHelperreturntrue}这个方法是在mParentView拦截事件后使用Handle拖动处理:publicvoidprocessTouchEvent(MotionEventev){...switch(action){...caseMotionEvent.ACTION_MOVE:{if(mDragState==STATE_DRAGGING){//IfpointerisinvalidthenskiptheACTION_MOVE.if(!isValidPointerForActionMove(mActivePointerId))break;finalintindex=ev.findPointerIndex(mActivePointerId);finalfloatx=ev.getX(index);finalfloaty=ev.getY(index);//计算距离上一次拖动的距离finalintidx=(int)(x-mLastMotionX[mActivePointerId]);finalintidy=(int)(y-mLastMotionY[mActivePointerId]);dragTo(mCapturedView.getLeft()+idx,mCapturedView.getTop()+idy,idx,idy);//处理拖动saveLastMotion(ev);//记录当前触摸点}...break;}...caseMotionEvent.ACTION_UP:{if(mDragState==STATE_DRAGGING){releaseViewForPointerUp();//释放拖动视图}cancel();break;}...}}(1)拖动ACTION_MOVE时,会计算指针距离上一次的位移,然后计算出捕获的View的目标位置,进行拖动处理;私人voiddragTo(intleft,inttop,intdx,intdy){intclampedX=left;intclampedY=top;finalintoldLeft=mCapturedView.getLeft();finalintoldTop=mCapturedView.getTop();如果(dx!=0){clampedX=mCallback.clampViewPositionHorizo??ntal(mCapturedView,left,dx);//通过回调得到真正的移动值ViewCompat.offsetLeftAndRight(mCapturedView,clampedX-oldLeft);//位移}if(dy!=0){clampedY=mCallback.clampViewPositionVertical(mCapturedView,top,dy);视图Compat.offsetTopAndBottom(mCapturedView,clampedY-oldTop);}if(dx!=0||dy!=0){finalintclampedDx=clampedX-oldLeft;finalintclampedDy=clampedY-oldTop;mCallback.onViewPositionChanged(mCapturedView,clampedX,clampedY,clampedDx,clampedDy);//Callback回调移动的位置}}实际移动的水平距离由callback的clampViewPositionHorizo??ntal方法决定,通常返回左边的值,即拖动多少就移动多少;通过回调的onViewPositionChanged方法,可以改变View拖动后的新位置做一些处理,比如;overridefunonViewPositionChanged(changedView:View?,left:Int,top:Int,dx:Int,dy:Int){super.onViewPositionChanged(changedView,left,top,dx,dy)//当新的左边位置达到宽度时,滑动界面,关闭页面if(left>=width&&contextisActivity&&!context.isFinishing){context.finish()}}(2)释放和ACTION_UP动作,释放拖动ViewprivatevoidreleaseViewForPointerUp(){...dispatchViewReleased(xvel,yvel);}privatevoiddispatchViewReleased(floatxvel,floatyvel){mReleaseInProgress=true;mCallback.onViewReleased(mCapturedView,xvel,yvel);//callback回调释放mReleaseInProgress=false;if(mDragState==STATE_DRAGGING){//onViewReleaseddidn'tcallamethodthatwouldhavechangedthis.Goidle.setDragState(STATE_IDLE);//重置状态}}通常在回调的onViewReleased方法中,我们可以判断当前释放点的位置,从而决定是否反弹页面或滑出屏幕overridefunonViewReleased(releasedChild:View?,xvel:Float,yvel:Float){super.onViewReleased(releasedChild,xvel,yvel)//滑动速度达到一定值后直接关闭if(xvel>=300){//将页面滑出屏幕并关闭页面dragHelper?.settleCapturedViewAt(width,0)}else{//回弹页面dragHelper?.settleCapturedViewAt(0,0)}//刷新,开始关闭或重置动画invalidate()}如果滑动速度大于300,我们调用settleCapturedViewAt方法将页面滚动出屏幕,否则调用该方法反弹(3)ScrollpublicbooleansettleCapturedViewAt(intfinalLeft,intfinalTop){returnforceSettleCapturedViewAt(finalLeft,finalTop,(int)mVelocityTracker.getXVelocity(mActivePointerId),(int)mVelocityTracker.getYPointerId(mActive));}privatebooleanforceSettleCapturedViewAt(intfinalLeft,intfinalTop,intxvel,intyvel){//当前位置finalintstartLeft=mCapturedView.getLeft();finalintstartTop=mCapturedView.getTop();//偏移finalintdx=finalLeft-startLeft;finalintdy=finalTop-startTop;...finalintduration=computeSettleDuration(mCapturedView,dx,dy,xvel,yvel);//使用Scroller对象开始滚动mScroller.startScroll(startLeft,startTop,dx,dy,duration);//重置状态为滚动setDragState(STATE_SETTLING);returntrue;}它内部使用了Scroller对象:它是View的滚动机制,它的回调是View的computeScroll()方法。其中,Scroller对象的computeScrollOffset方法用于判断滚动是否完成。如果还需要滚动,需要调用invalidate方法刷新;基于此,ViewDragHelper提供了一个类似的方法continueSettling,需要在computeScroll中调用,判??断是否需要invalidate;publicbooleancontinueSettling(booleandeferCallbacks){if(mDragState==STATE_SETTLING){//滚动是否结束booleankeepGoing=mScroller.computeScrollOffset();//当前滚动值finalintx=mScroller.getCurrX();finalinty=mScroller.getCurrY();//Offsetfinalintdx=x-mCapturedView.getLeft();finalintdy=y-mCapturedView.getTop();//廉价操作if(dx!=0){ViewCompat.offsetLeftAndRight(mCapturedView,dx);}if(dy!=0){ViewCompat.offsetTopAndBottom(mCapturedView,dy);}//回调if(dx!=0||dy!=0){mCallback.onViewPositionChanged(mCapturedView,x,y,dx,dy);}//滚动结束状态if(!keepGoing){if(deferCallbacks){mParentView.post(mSetIdleRunnable);}else{setDragState(STATE_IDLE);}}}returnmDragState==STATE_SETTLING;}在我们的视图中}以上是ViewDragHelperoverridefuncomputeScroll(){super.computeScroll()if(dragHelper?.continueSettling(true)==true){invalidate()}}上面是ViewDragHelper方法的实现原理和使用方法总结ViewDragHelper本质上是对MotionEvent的解析和处理,提供了一系列的监听和回调方法,帮助我们减轻开发负担,更方便的处理控件的滑动和拖拽逻辑;是不是觉得很简单,大家一起努力吧,女士们,先生们;