Android触摸事件的应用
常见的滑动冲突场景可以简单分为以下三种:
场景1:外部滑动方向和内部滑动方向不一致
场景2:外部滑动方向和内部滑动方向一致
场景3:上面两种情况的嵌套
如图:
场景1,主要是将ViewPager和Fragment配合使用所组成的页面滑动效果,主流应用几乎都会使用这个效果。在这个效果中可以通过左右滑动来切换页面,而每个页面内部往往又是一个ListView,所以就造成了滑动冲突,但是在ViewPager内部处理了这种滑动冲突,因此在采用ViewPager时我们就无须关注这个问题,而如果把ViewPager换成ScrollView,那就必须自己手动处理,不然造成的结果就是内外两层只能一层能够滑动。
场景2,就复杂一点,当内外两层都在同一个方向可以滑动的时候,显然存在逻辑问题。因为当手指开始滑动的时候,系统无法知道用户到底是想让哪一层滑动,所以当手指滑动的时候就会出现问题,要么只有一层滑动,要么就是内外两层都滑动但很卡顿。
场景3,是场景1和场景2两种情况的嵌套,显得更复杂了。比如外部有一个SlideMenu效果,内部有一个ViewPager,ViewPager的每一个页面中又是一个ListView。虽然场景3滑动冲突看起来很复杂,但都是几个单一的滑动冲突的叠加,因此需要一一拆解开来即可。
滑动冲突的处理规则一般来说,不管滑动冲突有多么复杂,它都有既定的规则,根据这些规则我们就可以选择合适的方法去处理。
对于场景1,它的处理规则就是:当用户左右滑动时,需要让外部的View拦截点击事件,当用户上下滑动,需要让内部View拦截点击事件。具体来说就是根据滑动是水平滑动还是竖直滑动来判断到底是由谁来拦截事件。
如图:
简单来说,就是根据水平方向和竖直方向的距离差来判断,如果是Dx>Dy,那么则是水平滑动,如果是Dy>Dx,那么则是竖直滑动。
场景2,则是比较特殊,它无法根据滑动的角度,距离差以及速度差来做判断。这个时候就需要从业务上找到突破点,比如,当处于某种状态时需要外部View响应用户的滑动,而处于另外一种状态时需要内部View来响应View的滑动
对于场景3的话,它的滑动规则也更复杂,和场景2一样,同样是从业务上找到突破点。
外部拦截法外部拦截法是指点击事件都是先经过父容器的拦截处理,如果父容器需要此事件就拦截,如果不需要此事件,就不拦截了,这样就可以解决滑动冲突的问题,外部拦截法需要重写父容器的onInterceptTouchEvent方法,在内部做相应的拦截即可,伪代码如下:
@OverridepublicbooleanonInterceptTouchEvent(MotionEventevent){booleanintercepted=false;intx=(int)event.getX();inty=(int)event.getY();switch(event.getAction()){caseMotionEvent.ACTION_DOWN:{intercepted=false;break;}caseMotionEvent.ACTION_MOVE:{if(父容器需要点击当前事件){intercepted=true;}else{intercepted=false;}break;}caseMotionEvent.ACTION_UP:{intercepted=false;break;}default:break;}mLastXIntercept=x;mLastYIntercept=y;returnintercepted;}
首先ACTION_DOWN这个事件,父容器必须返回false,这样保证后续move和up的事件可以传递给子View,根据move事件来决定是否拦截,如果父容器拦截就返回true,否则返回false。
实现一个自定义类似ViewPager的控件,嵌套ListView的效果,源代码如下:
publicclassHorizontalScrollViewExextendsViewGroup{privatestaticfinalStringTAG="HorizontalScrollViewEx";privateintmChildrenSize;privateintmChildWidth;privateintmChildIndex;//分别记录上次滑动的坐标privateintmLastX=0;privateintmLastY=0;//分别记录上次滑动的坐标(onInterceptTouchEvent)privateintmLastXIntercept=0;privateintmLastYIntercept=0;privateScrollermScroller;//弹性滑动对象privateVelocityTrackermVelocityTracker;//追踪滑动速度publicHorizontalScrollViewEx(Contextcontext){super(context);init();}publicHorizontalScrollViewEx(Contextcontext,AttributeSetattrs){super(context,attrs);init();}publicHorizontalScrollViewEx(Contextcontext,AttributeSetattrs,intdefStyle){super(context,attrs,defStyle);init();}privatevoidinit(){mScroller=newScroller(getContext());mVelocityTracker=VelocityTracker.obtain();}@OverridepublicbooleanonInterceptTouchEvent(MotionEventevent){booleanintercepted=false;intx=(int)event.getX();inty=(int)event.getY();switch(event.getAction()){caseMotionEvent.ACTION_DOWN:{intercepted=false;if(!mScroller.isFinished()){mScroller.abortAnimation();intercepted=true;}break;}caseMotionEvent.ACTION_MOVE:{intdeltaX=x-mLastXIntercept;intdeltaY=y-mLastYIntercept;if(Math.abs(deltaX)>Math.abs(deltaY)){intercepted=true;}else{intercepted=false;}break;}caseMotionEvent.ACTION_UP:{intercepted=false;break;}default:break;}Log.d(TAG,"intercepted="+intercepted);mLastX=x;mLastY=y;mLastXIntercept=x;mLastYIntercept=y;returnintercepted;}@OverridepublicbooleanonTouchEvent(MotionEventevent){mVelocityTracker.addMovement(event);intx=(int)event.getX();inty=(int)event.getY();switch(event.getAction()){caseMotionEvent.ACTION_DOWN:{if(!mScroller.isFinished()){mScroller.abortAnimation();}break;}caseMotionEvent.ACTION_MOVE:{intdeltaX=x-mLastX;scrollBy(-deltaX,0);break;}caseMotionEvent.ACTION_UP:{intscrollX=getScrollX();mVelocityTracker.computeCurrentVelocity(1000);floatxVelocity=mVelocityTracker.getXVelocity();if(Math.abs(xVelocity)>=50){mChildIndex=xVelocity>0?mChildIndex-1:mChildIndex+1;}else{mChildIndex=(scrollX+mChildWidth/2)/mChildWidth;}mChildIndex=Math.max(0,Math.min(mChildIndex,mChildrenSize-1));intdx=mChildIndex*mChildWidth-scrollX;smoothScrollBy(dx,0);mVelocityTracker.clear();break;}default:break;}mLastX=x;mLastY=y;returntrue;}@OverrideprotectedvoidonMeasure(intwidthMeasureSpec,intheightMeasureSpec){super.onMeasure(widthMeasureSpec,heightMeasureSpec);intmeasuredWidth=0;intmeasuredHeight=0;finalintchildCount=getChildCount();measureChildren(widthMeasureSpec,heightMeasureSpec);intwidthSpaceSize=MeasureSpec.getSize(widthMeasureSpec);intwidthSpecMode=MeasureSpec.getMode(widthMeasureSpec);intheightSpaceSize=MeasureSpec.getSize(heightMeasureSpec);intheightSpecMode=MeasureSpec.getMode(heightMeasureSpec);if(childCount==0){setMeasuredDimension(0,0);}elseif(heightSpecMode==MeasureSpec.AT_MOST){finalViewchildView=getChildAt(0);measuredHeight=childView.getMeasuredHeight();setMeasuredDimension(widthSpaceSize,childView.getMeasuredHeight());}elseif(widthSpecMode==MeasureSpec.AT_MOST){finalViewchildView=getChildAt(0);measuredWidth=childView.getMeasuredWidth()*childCount;setMeasuredDimension(measuredWidth,heightSpaceSize);}else{finalViewchildView=getChildAt(0);measuredWidth=childView.getMeasuredWidth()*childCount;measuredHeight=childView.getMeasuredHeight();setMeasuredDimension(measuredWidth,measuredHeight);}}@OverrideprotectedvoidonLayout(booleanchanged,intl,intt,intr,intb){intchildLeft=0;finalintchildCount=getChildCount();mChildrenSize=childCount;for(inti=0;i<childCount;i++){finalViewchildView=getChildAt(i);if(childView.getVisibility()!=View.GONE){finalintchildWidth=childView.getMeasuredWidth();mChildWidth=childWidth;childView.layout(childLeft,0,childLeft+childWidth,childView.getMeasuredHeight());childLeft+=childWidth;}}}privatevoidsmoothScrollBy(intdx,intdy){mScroller.startScroll(getScrollX(),0,dx,0,500);invalidate();}@OverridepublicvoidcomputeScroll(){if(mScroller.computeScrollOffset()){scrollTo(mScroller.getCurrX(),mScroller.getCurrY());postInvalidate();}}@OverrideprotectedvoidonDetachedFromWindow(){mVelocityTracker.recycle();super.onDetachedFromWindow();}}
这个情况的拦截条件就是父容器在滑动过程中水平距离差比垂直距离差大,那么就进行拦截,否则就不拦截,继续传递事件。
内部拦截法内部拦截法是指父容器不拦截任何事件,所有的事件都传递给子元素,如果子元素需要此事件就直接消耗掉,否则就交给父容器进行处理,这种方法和Android中的事件分发机制不一致,需要配合requestDisallowInterceptTouchEvent方法才能正常工作,使用起来较外部拦截法复杂。伪代码如下:
@OverridepublicbooleandispatchTouchEvent(MotionEventevent){intx=(int)event.getX();inty=(int)event.getY();switch(event.getAction()){caseMotionEvent.ACTION_DOWN:{mHorizontalScrollViewEx2.requestDisallowInterceptTouchEvent(true);break;}caseMotionEvent.ACTION_MOVE:{intdeltaX=x-mLastX;intdeltaY=y-mLastY;if(父容器需要此类点击事件){mHorizontalScrollViewEx2.requestDisallowInterceptTouchEvent(false);}break;}caseMotionEvent.ACTION_UP:{break;}default:break;}mLastX=x;mLastY=y;returnsuper.dispatchTouchEvent(event);}
当子元素调用requestDisallowInterceptTouchEvent(false)方法时,父元素才能继续拦截所需的事件。
前面是用自定义类似的ViewPager,现在重写一个ListView,我们可以自定义一个ListView,叫做ListViewEx,然后对内部拦截法的模板代码进行修改即可。
publicclassListViewExextendsListView{privatestaticfinalStringTAG="ListViewEx";privateHorizontalScrollViewEx2mHorizontalScrollViewEx2;//分别记录上次滑动的坐标privateintmLastX=0;privateintmLastY=0;publicListViewEx(Contextcontext){super(context);}publicListViewEx(Contextcontext,AttributeSetattrs){super(context,attrs);}publicListViewEx(Contextcontext,AttributeSetattrs,intdefStyle){super(context,attrs,defStyle);}publicvoidsetHorizontalScrollViewEx2(HorizontalScrollViewEx2horizontalScrollViewEx2){mHorizontalScrollViewEx2=horizontalScrollViewEx2;}@OverridepublicbooleandispatchTouchEvent(MotionEventevent){intx=(int)event.getX();inty=(int)event.getY();switch(event.getAction()){caseMotionEvent.ACTION_DOWN:{mHorizontalScrollViewEx2.requestDisallowInterceptTouchEvent(true);break;}caseMotionEvent.ACTION_MOVE:{intdeltaX=x-mLastX;intdeltaY=y-mLastY;Log.d(TAG,"dx:"+deltaX+"dy:"+deltaY);if(Math.abs(deltaX)>Math.abs(deltaY)){mHorizontalScrollViewEx2.requestDisallowInterceptTouchEvent(false);}break;}caseMotionEvent.ACTION_UP:{break;}default:break;}mLastX=x;mLastY=y;returnsuper.dispatchTouchEvent(event);}}
同时对于包含ListViewEx外部布局进行修改,在onInterceptTouchEvent事件上不进行拦截
publicclassHorizontalScrollViewEx2extendsViewGroup{privatestaticfinalStringTAG="HorizontalScrollViewEx2";privateintmChildrenSize;privateintmChildWidth;privateintmChildIndex;//分别记录上次滑动的坐标privateintmLastX=0;privateintmLastY=0;//分别记录上次滑动的坐标(onInterceptTouchEvent)privateintmLastXIntercept=0;privateintmLastYIntercept=0;privateScrollermScroller;privateVelocityTrackermVelocityTracker;publicHorizontalScrollViewEx2(Contextcontext){super(context);init();}publicHorizontalScrollViewEx2(Contextcontext,AttributeSetattrs){super(context,attrs);init();}publicHorizontalScrollViewEx2(Contextcontext,AttributeSetattrs,intdefStyle){super(context,attrs,defStyle);init();}privatevoidinit(){mScroller=newScroller(getContext());mVelocityTracker=VelocityTracker.obtain();}@OverridepublicbooleanonInterceptTouchEvent(MotionEventevent){intx=(int)event.getX();inty=(int)event.getY();intaction=event.getAction();if(action==MotionEvent.ACTION_DOWN){mLastX=x;mLastY=y;if(!mScroller.isFinished()){mScroller.abortAnimation();returntrue;}returnfalse;}else{returntrue;}}@OverridepublicbooleanonTouchEvent(MotionEventevent){Log.d(TAG,"onTouchEventaction:"+event.getAction());mVelocityTracker.addMovement(event);intx=(int)event.getX();inty=(int)event.getY();switch(event.getAction()){caseMotionEvent.ACTION_DOWN:{if(!mScroller.isFinished()){mScroller.abortAnimation();}break;}caseMotionEvent.ACTION_MOVE:{intdeltaX=x-mLastX;intdeltaY=y-mLastY;Log.d(TAG,"move,deltaX:"+deltaX+"deltaY:"+deltaY);scrollBy(-deltaX,0);break;}caseMotionEvent.ACTION_UP:{intscrollX=getScrollX();intscrollToChildIndex=scrollX/mChildWidth;Log.d(TAG,"currentindex:"+scrollToChildIndex);mVelocityTracker.computeCurrentVelocity(1000);floatxVelocity=mVelocityTracker.getXVelocity();if(Math.abs(xVelocity)>=50){mChildIndex=xVelocity>0?mChildIndex-1:mChildIndex+1;}else{mChildIndex=(scrollX+mChildWidth/2)/mChildWidth;}mChildIndex=Math.max(0,Math.min(mChildIndex,mChildrenSize-1));intdx=mChildIndex*mChildWidth-scrollX;smoothScrollBy(dx,0);mVelocityTracker.clear();Log.d(TAG,"index:"+scrollToChildIndex+"dx:"+dx);break;}default:break;}mLastX=x;mLastY=y;returntrue;}@OverrideprotectedvoidonMeasure(intwidthMeasureSpec,intheightMeasureSpec){super.onMeasure(widthMeasureSpec,heightMeasureSpec);intmeasuredWidth=0;intmeasuredHeight=0;finalintchildCount=getChildCount();measureChildren(widthMeasureSpec,heightMeasureSpec);intwidthSpaceSize=MeasureSpec.getSize(widthMeasureSpec);intwidthSpecMode=MeasureSpec.getMode(widthMeasureSpec);intheightSpaceSize=MeasureSpec.getSize(heightMeasureSpec);intheightSpecMode=MeasureSpec.getMode(heightMeasureSpec);if(childCount==0){setMeasuredDimension(0,0);}elseif(heightSpecMode==MeasureSpec.AT_MOST){finalViewchildView=getChildAt(0);measuredHeight=childView.getMeasuredHeight();setMeasuredDimension(widthSpaceSize,childView.getMeasuredHeight());}elseif(widthSpecMode==MeasureSpec.AT_MOST){finalViewchildView=getChildAt(0);measuredWidth=childView.getMeasuredWidth()*childCount;setMeasuredDimension(measuredWidth,heightSpaceSize);}else{finalViewchildView=getChildAt(0);measuredWidth=childView.getMeasuredWidth()*childCount;measuredHeight=childView.getMeasuredHeight();setMeasuredDimension(measuredWidth,measuredHeight);}}@OverrideprotectedvoidonLayout(booleanchanged,intl,intt,intr,intb){Log.d(TAG,"width:"+getWidth());intchildLeft=0;finalintchildCount=getChildCount();mChildrenSize=childCount;for(inti=0;i<childCount;i++){finalViewchildView=getChildAt(i);if(childView.getVisibility()!=View.GONE){finalintchildWidth=childView.getMeasuredWidth();mChildWidth=childWidth;childView.layout(childLeft,0,childLeft+childWidth,childView.getMeasuredHeight());childLeft+=childWidth;}}}privatevoidsmoothScrollBy(intdx,intdy){mScroller.startScroll(getScrollX(),0,dx,0,500);invalidate();}@OverridepublicvoidcomputeScroll(){if(mScroller.computeScrollOffset()){scrollTo(mScroller.getCurrX(),mScroller.getCurrY());postInvalidate();}}@OverrideprotectedvoidonDetachedFromWindow(){mVelocityTracker.recycle();super.onDetachedFromWindow();}}
这个拦截规则也是父容器在滑动过程中水平距离差与垂直距离差相比。
声明:本站所有文章资源内容,如无特殊说明或标注,均为采集网络资源。如若本站内容侵犯了原著者的合法权益,可联系本站删除。