Detector的意思就是探测者,所以GestureDetector就是用来监听手势的发生。它内部有3个Listener接口,用来回调不同类型的触摸事件,用一个简略的类图来显示:
里面这些接口的方法,就是相应触摸事件的回调,实现了这些方法,就能实现传入触摸事件之后做出相应的回调。
1.OnGestureListener,这个Listener监听一些手势,如单击、滑动、长按等操作:
- onDown(MotionEvent e):用户按下屏幕的时候的回调。
- onShowPress(MotionEvent e):用户按下按键后100ms(根据Android7.0源码)还没有松开或者移动就会回调,官方在源码的解释是说一般用于告诉用户已经识别按下事件的回调(我暂时想不出有什么用途,因为这个回调触发之后还会触发其他的,不像长按)。
- onLongPress(MotionEvent e):用户长按后(好像不同手机的时间不同,源码里默认是100ms+500ms)触发,触发之后不会触发其他回调,直至松开(UP事件)。
- onScroll(MotionEvent e1, MotionEvent e2,float distanceX, float distanceY):手指滑动的时候执行的回调(接收到MOVE事件,且位移大于一定距离),e1,e2分别是之前DOWN事件和当前的MOVE事件,distanceX和distanceY就是当前MOVE事件和上一个MOVE事件的位移量。
- onFling(MotionEvent e1, MotionEvent e2, float velocityX,float velocityY):用户执行抛操作之后的回调,MOVE事件之后手松开(UP事件)那一瞬间的x或者y方向速度,如果达到一定数值(源码默认是每秒50px),就是抛操作(也就是快速滑动的时候松手会有这个回调,因此基本上有onFling必然有onScroll)。
- onSingleTapUp(MotionEvent e):用户手指松开(UP事件)的时候如果没有执行onScroll()
和onLongPress()
这两个回调的话,就会回调这个,说明这是一个点击抬起事件,但是不能区分是否双击事件的抬起。
2.OnDoubleTapListener,这个Listener监听双击和单击事件。
- onSingleTapConfirmed(MotionEvent e):可以确认(通过单击DOWN后300ms没有下一个DOWN事件确认)这不是一个双击事件,而是一个单击事件的时候会回调。
- onDoubleTap(MotionEvent e):可以确认这是一个双击事件的时候回调。
- onDoubleTapEvent(MotionEvent e):onDoubleTap()
回调之后的输入事件(DOWN、MOVE、UP)都会回调这个方法(这个方法可以实现一些双击后的控制,如让View双击后变得可拖动等)。
3.OnContextClickListener,很多人都不知道ContextClick是什么,我以前也不知道,直到我把平板接上了外接键盘——原来这就是鼠标右键。。。
- onContextClick(MotionEvent e):当鼠标/触摸板,右键点击时候的回调。
4.SimpleOnGestureListener,实现了上面三个接口的类,拥有上面三个的所有回调方法。
- 由于SimpleOnGestureListener不是抽象类,所以继承它的时候只需要选取我们所需要的回调方法来重写就可以了,非常方便,也减少了代码量,符合接口隔离原则,也是模板方法模式的实现。而实现上面的三个接口中的一个都要全部重写里面的方法,所以我们一般都是选择SimpleOnGestureListener。
使用:实现OnGestureListener接口
滑动时方法的调用顺序:onDown(点击时) -》onScroll(按下)-》 onFling(抬起)
private static final int SWIPE_MIN_DISTANCE = 120;
private static final int SWIPE_MAX_OFF_PATH = 250;
private static final int SWIPE_THRESHOLD_VELOCITY = 200;
我们要实现左右滑动需要三个数据:
SWIPE_MIN_DISTANCE = 120; //表示x轴至少滑动120才会触发
SWIPE_MAX_OFF_PATH = 250; //表示y轴最多滑动250才会触发,以保证是在横向滑动
SWIPE_THRESHOLD_VELOCITY = 200; //x轴方向的距离
class GestureListener implements OnGestureListener{
private static final int SWIPE_MIN_DISTANCE = 120;
private static final int SWIPE_MAX_OFF_PATH = 250;
private static final int SWIPE_THRESHOLD_VELOCITY = 200;
public boolean onSingleTapUp(MotionEvent e) {
// TODO Auto-generated method stub
return false;
}
@Override
public void onShowPress(MotionEvent e) {
// TODO Auto-generated method stub
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
// TODO Auto-generated method stub
return false;
}
@Override
public void onLongPress(MotionEvent e) {
// TODO Auto-generated method stub
}
@Override
public boolean onDown(MotionEvent e) {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
float velocityY) {
if (Math.abs(e1.getY() - e2.getY()) > SWIPE_MAX_OFF_PATH){ //纵向滑动,返回错误
return false;
}
if (e1.getX() - e2.getX() > SWIPE_MIN_DISTANCE && Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) {
Log.e("Calendar", "left--->right") ;
return true;
} else if (e2.getX() - e1.getX() > SWIPE_MIN_DISTANCE && Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) {
Log.e("Calendar", "right--->left") ;
return true;
}
return false;
}
}
当然不要忘记在主函数中实现onTouch,事件分发机制;
下面复习一下ViewGroup和View的onTouch事件分发机制:
接收事件顺序:
Activity—->ViewGroup(页面布局)—->view或ViewGroup(页面子布局)
首先当Activity 收到down事件,会将事件发给viewgroup,viewgroup会判断自身是否拦截,如果拦截了则自身消费,之后的的move,up事件将交给所拦截的viewgroup处理(除非上一级进行了拦截,则会接收到取消事件),如果不拦截则向子布局传递事件,子view如果return true,则证明消费了改事件,以后的move,up事件将交给改view处理,除非上一级将move或up处理;如果所有布局均不消费该事件,则会
将所有view或viewgroup走一遍,而后执行Activity的onTouchEvent方法,如果所有视图层均未消费事件,则以后的move,up事件将全部由Activity消费,不再向下传递。
另外,一个事件只能有一个视图消费,不能多个view或者viewgroup共同消费。
由于网上完整的事件分发机制已经太多太全了,所以本文介绍的比较浅显,如果大家有疑惑可以去网上搜索事件分发详解
@Override
public boolean onTouch(View v, MotionEvent event) {
return mGesture.onTouchEvent(event);
}