1.GestureDetecor
用户触摸屏幕时会产生许多手势,一般通过重写View类的onTouch()方法可以处理一些触摸事件,但是这个方法太过简单,如果需要处理一些复杂的手势,用这个接口就会很麻烦,需要自己根据用户触摸的轨迹去判断是什么手势。
因此,Android sdk提供了GestureDetector类来监听手势发生,通过它的onTouchEvent(event)方法完成不同手势的识别。识别出手势后,不同的手势要怎么处理,再由开发者自己来实现。
GestureDetector类内部定义了三个接口和一个类,重写这些方法,就能实现传入触摸事件之后做出相应的回调:
①OnGestureListener接口
监听一些手势,如单击、滑动、长按等操作。
②OnDoubleTapListener接口
监听双击和单击事件。
③OnContextClickListener接口
当鼠标/触摸板,右键点击时候的回调。
④SimpleOnGestureListener 类
实现了上面三个接口的类,拥有上面三个的所有回调方法。
这些回调方法的返回值都是boolean类型,和View的事件传递机制一样,返回true表示消耗了事件,flase表示没有消耗。
其中,SimpleOnGestureListener是一个静态内部类,它实现了OnGestureListener、OnDoubleTapListener、OnContextClickListener三个接口。SimpleOnGestureListener类内重写了接口中的所有方法,但都是空实现,返回的布尔值都是false。这个类的主要作用就是方便继承这个类有选择的复写回调方法,而不是实现接口去重写所有的方法。
下面具体说说这几个接口:
①GesturetDetector.OnGestureListener 接口
public interface OnGestureListener {
boolean onDown(MotionEvent e); // 由1个down触发,每按一下屏幕立即触发
void onShowPress(MotionEvent e); //按下屏幕并且没有移动或松开,由1个down触发。注意和onDown()的区别,强调的是没有松开或者拖动的状态,而onDown()没有任何限制。主要是提供给用户一个可视化的反馈,告诉用户按下操作已经被捕捉到了。如果按下的速度很快只会调用onDown(),按下的速度稍慢一点会先调用onDown()再调用onShowPress()
boolean onSingleTapUp(MotionEvent e); //一次单纯的轻击抬手动作时触发,由一个up触发。如果除了Down以外还有其他操作,比如触发了长按,这时候手抬起时,这个方法不执行
boolean onScroll(MotionEvent e1, MotionEvent e2,float distanceX,float distanceY); // 屏幕拖动事件。手指按下并在屏幕上滑动时触发。这个事件由1个down,多个move触发。手指在屏幕上滑动时,会不断触发该方法。如果按下的时间过长,调用了onLongPress,再拖动屏幕不会触发onScroll。e1:开始拖动的第一次按下down操作,也就是第一个ACTION_DOWN;e2:触发当前onScroll方法的ACTION_MOVE,即当前滑动位置点;distanceX:当前x坐标与最后一次触发scroll方法的x坐标的差值,不是e1点到e2点的x轴距离;diastancY:当前y坐标与最后一次触发scroll方法的y坐标的差值,不是e1点到e2点的y轴距离
void onLongPress(MotionEvent e); //长按。在down操作之后,过一个特定的时间触发
boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY); //按下屏幕,在屏幕上快速滑动后松开,由一个down、多个move、一个up触发。当滑动较慢时,只会触发onScroll,而不会触发onFling。e1:开始快速滑动的第一次按下down操作,也就是第一个ACTION_DOWN,即拖动事件的起点;e2:触发当前onFling方法的move操作,也就是最后一个ACTION_MOVE,即onFling()调用时手指的位置;velocityX:X轴上的移动速度,像素/秒;velocityY:Y轴上的移动速度,像素/秒
}
快按屏幕抬起:onDown —> onSingleTapUp —> onSingleTapConfirmed
慢按屏幕抬起:onDown –> onShowPress —> onSingleTapUp —> onSingleTapConfirmed
按下屏幕等待一段时间:onDown –> onShowPress –> onLongPress
拖动屏幕:onDown –> onShowPress –> onScroll(多个) –> onFling
快速滑动:onDown–>onScroll(多个)–>onFling
注意:当拖动速率velocityX或velocityY超过ViewConfiguration.getMinimumFlingVelocity()最小拖动速率时,才会调用onFling(),也就是说如果只拖动一点,或者慢慢的拖动,是不会触发该方法的。
②GesttureDetector.OnDoubleTapListener 接口
public interface OnDoubleTapListener {
boolean onSingleTapConfirmed(MotionEvent e); //单击事件。用来判定该次点击是单纯的SingleTap而不是DoubleTap。如果连续点击两次就是DoubleTap手势,如果只点击一次,系统等待一段时间后没有收到第二次点击则判定该次点击为SingleTap而不是DoubleTap,然后触发SingleTapConfirmed事件。触发顺序是:OnDown->OnSingleTapUp->OnSingleTapConfirmed。关于onSingleTapConfirmed和onSingleTapUp的区别:onSingleTapUp只要手抬起就会执行,而对于onSingleTapConfirmed来说,如果双击的话,则onSingleTapConfirmed不会执行。
boolean onDoubleTap(MotionEvent e); //双击事件。在第二次点击的down时触发。参数表示双击发生时,第一次按下时的down事件
boolean onDoubleTapEvent( MotionEvent e); //双击间隔中发生的动作。指触发onDoubleTap以后,在双击之间发生的其它动作,包含down、up和move事件
}
下图是双击一下的Log输出:
从图中可以看出,在第二下点击时,先触发OnDoubleTap,然后再触发OnDown(第二次点击)。在触发OnDoubleTap以后,就开始触发onDoubleTapEvent了,onDoubleTapEvent后面的数字代表了当前的事件,0指ACTION_DOWN,1指ACTION_UP,2 指ACTION_MOVE。
双击顺序:onDown –> onSingleTapUp –> onDoubleTap –> onDoubleTapEvent –> onDown–> onDoubleTapEvent
③GesttureDetector.OnContextClickListener接口
public interface On context click Listener {
boolean onContextClick(MotionEvent e);
}
④GestureDetector.SimpleOnGestureListener类
它与前面三个不同,这是一个类,并且实现了前三个接口,在它基础上新建类的话,要用extends派生而不是用implements继承。OnGestureListener和OnDoubleTapListener接口里的函数都是必须重写的,即使用不到也要重写一个空函数,但SimpleOnGestureListener类的实例或派生类中可以根据情况,用到哪个函数就重写哪个函数,因为SimpleOnGestureListener类本身已经实现了这两个接口的所有函数,只是里面全是空的而已。
2.GestureDetector使用方法
①第一步:根据需求创建onGestureListener、onDoubleTapListener或simpleOnGestureListener这三个类中的一个对象。比如只需要监听单击的话,那只需要得到实现了onGestureListener接口的对象;如果是双击,就只需要得到实现了onDoubleTapListener接口的对象;如果两种情况都需要考虑,就可以用SimpleOnGestureListener对象或者一个实现了上面两个接口的对象。
②第二步:创建一个GestureDetector对象,参数传入第一步的对象。
GestureDetector的构造函数如下:
public GestureDetector(Context context, OnGestureListener listener) {
this(context, listener, null);
}
public GestureDetector(Context context, OnGestureListener listener, Handler handler) {
}
public GestureDetector(Context context, OnGestureListener listener, Handler handler,
boolean unused) {
this(context, listener, handler);
}
mGestureDetector = new GestureDetector( mContext, new MyGestureListener());
③第三步: 给view设置OnTouchListener监听,重写onTouch()方法,在return语句中写上mGestureDetector.onTouchEvent(event),将控制权交给GestureDector。
④第四步:绑定控件,给view设置setClickable(true)、setFocusable(true)、setLongClickable(true)。这一步非常重要,不然出不来效果。
注意:GestureDetector的构造方法中,有的构造函数需要多传递一个Handler作为参数,这个Handler主要是为了给GestureDetector提供一个Looper。带有handler参数,表示要在与该handler相关联的线程上,监听手势并处理延时事件。
通常情况下不需要这个Handler,因为它会在内部自动创建一个Handler用于处理数据。如果在主线程中创建GestureDetector,那么它内部创建的Handler会自动获得主线程的Looper;但是如果在一个没有创建Looper的子线程中创建 GestureDetector ,则需要传递一个带有Looper的Handler给它,否则就会因为无法获取到Looper导致创建失败。
下面是两种在子线程中创建GestureDetector的方法:
// 方式一:在主线程创建Handler
final Handler handler = new Handler();
new Thread(new Runnable() {
@Override
public void run() {
final GestureDetector detector = new GestureDetector(this, new GestureDetector.SimpleOnGestureListener() , handler);
// ......
}
}).start();
// 方式二:在子线程创建Handler,并指定Looper
new Thread(new Runnable() {
@Override
public void run() {
final Handler handler = new Handler(Looper.getMainLooper());
final GestureDetector detector = new GestureDetector(this, new GestureDetector.SimpleOnGestureListener() , handler);
// ......
}
}).start();
使用其它创建Handler的方式也可以,重点是传递的Handler一定要有Looper。强调一下:重点是Handler中的Looper。假如子线程准备了Looper,则可以直接使用第 1 种构造函数进行创建:
new Thread(new Runnable() {
@Override public void run() {
Looper.prepare(); // <- 重点在这里
final GestureDetector detector = new GestureDetector(this, new GestureDetector.SimpleOnGestureListener());
// ... 省略其它代码 ...
}
}).start();
GestureDetector使用 完整代码:
public class MyActivity extends Activity implements View.OnTouchListener, GestureDetector.OnGestureListener,GestureDetector.OnDoubleTapListener{
private GestureDetector mGestureDetector;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mGestureDetector = new GestureDetector(this);
Text view textview = findViewById(R.id.tv);
textview.setOnTouchListener(this);
textview.setClickable(true);
textview.setLongClickable(true);
textview.setFocusable(true); mGestureDetector.setIsLongpressEnabled(true);
}
@Override
public boolean onTouch(View v, MotionEvent event) {
return mGestureDetector. onTouchEvent(event);
}
@Override
public boolean onDown(MotionEvent e) {
return true;//注意要返回true,否则后续事件无法处理
}
@Override
public void onShowPress(MotionEvent e) {
Log.e(TAG, "onShowPress");
}
@Override
public boolean onSingleTapUp(MotionEvent e) {
Log.e(TAG, "onSingleTapUp");
return true;
}
@Override
public void onLongPress(MotionEvent e) {
Log.e(TAG, "onLongPress");
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,float distanceY){
Log.e(TAG, "onScroll");
return true;
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
Log.e(TAG, "onFling");
final int FLING_MIN_DISTANCE = 100, FLING_MIN_VELOCHTE = 200;
if(e1.getX() - e2.getX() > FLING_MIN_DISTANCE && Math.abs(velocityX) > FLING_MIN_VELOCITY) {
Log.i(TAG, "Fling left");
} else if(e2.getX() - e1.getX() > FLING_MIN_DISTANCE && Math.abs(velocityX) > FLING_MIN_VELOCITY) {
Log.i(TAG, "Fling right");
} else if(e1.getY() - e2.getY() > FLING_MIN_DISTANCE && Math.abs(velocityY) > FLING_MIN_VELOCITY) {
Log.i(TAG, "Fling up");
} else if(e2.getY() - e1.getY() > FLING_MIN_DISTANCE && Math.abs(velocityY) > FLING_MIN_VELOCITY) {
Log.i(TAG, "Fling down");
}
return false;
}
@Override
public boolean onDoubleTap(MotionEvent e) {
Log.e(TAG, "onDoubleTap");
return true;
}
@Override
public boolean onDoubleTapEvent( MotionEvent e) {
Log.e(TAG, "onDoubleTapEvent");
return true;
}
@Override
public boolean onSingleTapConfirmed( MotionEvent e) {
Log.e(TAG, "onSingleTapConfirmed");
return true;
}
@Override
public boolean onContextClick(MotionEvent e) {
Log.e(TAG, "onContextClick");
return true;
}
@override
public boolean dispatchTouchEvent( MotionEvent event){
if(mGestureDetector.onTouchEvent(event)){ //返回true说明手势处理了这个事件
event.setAction(MotionEvent. ACTION_CANCEL);
}
return super.dispatchTouchEvent(event);
}
}
注意:最后这个dispatchTouchEvent方法必须要写,否则onScroll、onFling方法不执行。原因还在研究中。而且,要想触发onScroll、onFling方法,必须让监听器的onDown返回值是true。
3.GestureDetector源码
①初始化处理
从构造方法开始,目前在用的有三个:
public GestureDetector(Context context, OnGestureListener listener) {
this(context, listener, null);
}
public GestureDetector(Context context, OnGestureListener listener, Handler handler, boolean unused) {
this(context, listener, handler);
}
public GestureDetector(Context context, OnGestureListener listener, Handler handler) {
if (handler != null) {
mHandler = new GestureHandler(handler);//初始化Handler,用于处理延时消息
} else {
mHandler = new GestureHandler();
}
mListener = listener;//设置回调监听器
if (listener instanceof OnDoubleTapListener) {
setOnDoubleTapListener( (OnDoubleTapListener) listener);
}
if (listener instanceof OnContextClickListener){
setContextClickListener( (OnContextClickListener) listener);
}
init(context);
}
private void init(Context context) {
if (mListener == null) {
throw new NullPointerException( "OnGestureListener must not be null");
}
mIsLongpressEnabled = true;
int touchSlop, doubleTapSlop, doubleTapTouchSlop;
if (context == null) {
touchSlop = ViewConfiguration. getTouchSlop(); //滑动的时候,滑动数值大于这个值才认为滑动开始
doubleTapTouchSlop = touchSlop; //点击的时候,手指移动大于这个距离,就被认为不可能是双击
doubleTapSlop = ViewConfiguration. getDoubleTapSlop();//双击位置之间的最大距离,大于这个值就不认为是双击
mMinimumFlingVelocity = ViewConfiguration.getMinimumFlingVelocity();//手指抛的最小速度
mMaximumFlingVelocity = ViewConfiguration.getMaximumFlingVelocity();//手指抛的最大速度
} else {
final ViewConfiguration configuration = ViewConfiguration.get(context);
touchSlop = configuration. getScaledTouchSlop();
doubleTapTouchSlop = configuration.getScaledDoubleTapTouchSlop();
doubleTapSlop = configuration. getScaledDoubleTapSlop();
mMinimumFlingVelocity = configuration. getScaledMinimumFlingVelocity();
mMaximumFlingVelocity = configuration. getScaledMaximumFlingVelocity();
}
mTouchSlopSquare = touchSlop * touchSlop;//做平方好计算距离,后面的距离对比也是用平方
mDoubleTapTouchSlopSquare = doubleTapTouchSlop * doubleTapTouchSlop;
mDoubleTapSlopSquare = doubleTapSlop * doubleTapSlop;
}
GestureDetector的创建就是初始化一些属性,获取一些数值作为阈值,然后把对应的Listener设置好。此外,注意到初始化了mHandler。mHandler是GestureHandler的一个实例,它是控制onShowPress()、onLongPress()、onSingleTapConfirmed()回调的关键:
private class GestureHandler extends Handler {
//两个构造方法,一个带参数handler,一个不带
GestureHandler() {
super();
}
GestureHandler(Handler handler) {
super(handler.getLooper());
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case SHOW_PRESS:
mListener.onShowPress( mCurrentDownEvent); //回调onShowPress方法
break;
case LONG_PRESS:
dispatchLongPress();//处理长按消息
break;
case TAP:
// 控制SingleTapConfirmed的回调
if (mDoubleTapListener != null) {
if (!mStillDown) { //此时手指已抬起,回调确认单击的方法
mDoubleTapListener. onSingleTapConfirmed(mCurrentDownEvent);
} else { //手指未抬起,那么就在手指抬起的时候回调确认单击的方法。也就是说如果处理Message时手指还没松开,就设置mDeferConfirmSingleTap为true,在UP事件的时候调用SingleTapConfirme
mDeferConfirmSingleTap = true;
}
}
break;
default:
throw new RuntimeException("Unknown message " + msg);
}
}
}
在handler中调用了dispatchLongPress方法:
private void dispatchLongPress() { //长按处理
mHandler.removeMessages(TAP);
mDeferConfirmSingleTap = false;
mInLongPress = true;
mListener.onLongPress(mCurrentDownEvent);
}
②onTouchEvent
这是GestureDetector的核心逻辑,也就是如何处理手势信息,并判断是具体的哪种手势:
public boolean onTouchEvent(MotionEvent ev) {
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier. onTouchEvent(ev, 0); //检查事件输入的一致性,log出来一致性的信息,如:有事件只有up没有down
}
final int action = ev.getAction();
if(mCurrentMotionEvent != null) {
mCurrentMotionEvent.recycle();
}
mCurrentMotionEvent=MotionEvent.obtain(ev);
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
}
mVelocityTracker.addMovement(ev); //初始化速度追踪器,并计算速度
//检测action是否有非主要手指抬起来了,如果是的话,记录它的index,下面计算的时候忽略这个触点
final boolean pointerUp = (action & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_POINTER_UP;
final int skipIndex = pointerUp ? ev.getActionIndex() : -1;
final boolean isGeneratedGesture = (ev.get flag & MotionEvent.FLAG_IS_GENERATED_GESTURE) != 0;
float sumX = 0, sumY = 0;
final int count = ev.getPointerCount();
//下面需要得出x、y方向上的中心焦点。如果是多点触碰,则把所有还在触摸的手指的位置x、y加起来,后面求平均数,算出中心焦点
for (int i = 0; i < count; i++) {
if (skipIndex == i) continue; // 非主要手指抬起的话会跳过
sumX += ev.getX(i);
sumY += ev.getY(i);
}
final int div = pointerUp ? count - 1 : count; //pointerUp为true,需要排除掉
final float focusX = sumX / div; //中心焦点x
final float focusY = sumY / div;//中心焦点y
boolean handled = false;
switch (action & MotionEvent.ACTION_MASK){
case MotionEvent.ACTION_POINTER_DOWN:
//...
break;
case MotionEvent.ACTION_POINTER_UP:
//...
break;
case MotionEvent.ACTION_DOWN:
//...
break;
case MotionEvent.ACTION_MOVE:
//...
break;
case MotionEvent.ACTION_UP:
//...
break;
case MotionEvent.ACTION_CANCEL:
cancel();
break;
}
//对未被处理的事件进行一次一致性检测
if (!handled && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier. onUnhandledEvent(ev, 0);
}
return handled;
}
onTouchEvent()的主要思路就是先对输入事件做出统一处理,提取一些共有的信息,如多个点同时触摸时候的中心焦点和滑动速度等,然后根据事件的分类做出相应的处理。
注意:InputEventConsistencyVerifier对输入事件进行的一致性检测的结果并不影响GestureDetector的运行,如果检测到一致性不符合的事件(只有UP事件而前面没有DOWN事件),就只会输出log告诉开发者。
接下来看具体每个event是如何处理的。
③DOWN事件处理
case MotionEvent.ACTION_DOWN:
if (mDoubleTapListener != null) { //设置了双击监听器
//第一次点击的时候发送了TAP类型的消息,取消掉,防止出错
boolean hadTapMessage = mHandler.hasMessages(TAP);
if (hadTapMessage)
mHandler.removeMessages(TAP);
if ((mCurrentDownEvent != null) && (mPreviousUpEvent != null) && hadTapMessage && isConsideredDoubleTap( mCurrentDownEvent, mPreviousUpEvent, ev)) { //判断到这是第二次点击
mIsDoubleTapping = true;
handled |= mDoubleTapListener.onDou bleTap(mCurrentDownEvent); //回调双击方法,参数用的是双击里的第一次tap的down事件
handled |= mDoubleTapListener.onDou bleTapEvent(ev); //回调双击方法,参数用的是双击里第二次tap的down事件
} else {
//延时发出单击事件,如果到了时间还没有取消的话就确认是TAP事件了
mHandler.sendEmptyMessageDelayed( TAP, DOUBLE_TAP_TIMEOUT); //这是第一次的点击,先发送延时消息,如果时间到了(300ms)还没有取消的话(也就是没有再次点击),GestureDetector就会处理这个TAP类型的消息
}
}
mDownFocusX = mLastFocusX = focusX;
mDownFocusY = mLastFocusY = focusY;
//重置mCurrentDownEvent
if (mCurrentDownEvent != null) {
mCurrentDownEvent.recycle();
}
mCurrentDownEvent = MotionEvent.obtain(ev);
mAlwaysInTapRegion = true;
mAlwaysInBiggerTapRegion = true;
mStillDown = true;
mInLongPress = false;
mDeferConfirmSingleTap = false;
if (mIsLongpressEnabled) { //允许长按
mHandler.removeMessages(LONG_PRESS);
mHandler.sendEmptyMessageAtTime( LONG_PRESS,mCurrentDownEvent.getDownTime() + TAP_TIMEOUT + LONGPRESS_TIMEOUT); //发送延时的长按消息,在TAP_TIMEOUT+ LONGPRESS_TIMEOUT这段时间内,如果没有其他手势操作(移动手指、抬起手指),就会认为是长按手势,并且回调LongPress方法
} mHandler.sendEmptyMessageAtTime( SHOW_PRESS,mCurrentDownEvent.getDownTime() + TAP_TIMEOUT); //一小段时间后,延时发送showPress事件,反馈给用户
handled |= mListener.onDown(ev);
break;
down事件处理完了,里面有一个判断是不是双击的方法isConsideredDoubleTap,该方法用于判断第二次点击是否有效双击:
private boolean isConsideredDoubleTap( MotionEvent firstDown, MotionEvent firstUp, MotionEvent secondDown) {
if (!mAlwaysInBiggerTapRegion) { //第一次点击后,超出了限定范围,认为无效
return false;
}
final long deltaTime = secondDown. getEventTime() - firstUp.getEventTime();
if (deltaTime > DOUBLE_TAP_TIMEOUT || deltaTime < DOUBLE_TAP_MIN_TIME) { //第一次点击的手指抬起和第二次点击的手指落下,中间时间间隔不符合要求,无效
return false;
}
int deltaX = (int) firstDown.getX() - (int) secondDown.getX();
int deltaY = (int) firstDown.getY() - (int) secondDown.getY();
//判断第二次点击是否在附近,在附近才被认为是双击
return (deltaX * deltaX + deltaY * deltaY < mDoubleTapSlopSquare);
}
对DOWN事件的处理逻辑:
1)单击判断:如果收到一次DOWN事件,而且前段时间没有DOWN事件的话,会发送一个延时的TAP信息,一段时间(300ms)之后没有被取消的话,就执行GestureHandler里面的TAP单击确认操作。
2)双击判断:如果前面也有一次DOWN事件,而且符合isConsideredDoubleTap()的条件(第一次点击后没有移动超出范围,第二次点击也在附近),就可以确认双击,执行onDoubleTap()和onDoubleTapEvent()的回调。
3)长按判断:先看用户是否允许检测长按,如果允许长按,在点击的时候就发送一个延时的长按消息,如果时间到了还没被取消的话就回调长按方法。
4)showPress判断:这个和长按差不多,就是时间(100ms)短了一点而已。
注意:handled是boolean变量,|=符号是用符号右边的值跟左边的值进行或运算再赋值给左边。a |= b等价于a = a | b,这里handled变量初始值为false,进行了多次|=操作,一旦有结果是true的话,handled最后的值就是true,所有结果都是false最后才会false,onTouchEvent()方法最后返回这个handled,就可以表示事件有没有被处理,实现了对事件处理的封装。
④MOVE事件处理
case MotionEvent.ACTION_MOVE:
if (mInLongPress || mInContextClick) {
break; //已经触发长按操作,一系列事件可以认为是长按,接下来不做其他处理
}
final float scrollX = mLastFocusX - focusX;
final float scrollY = mLastFocusY - focusY;
if (mIsDoubleTapping) { //双击过程中,会回调onDoubleTapEvent方法
handled |= mDoubleTapListener. onDoubleTapEvent(ev);
} else if (mAlwaysInTapRegion) { //在down事件里才会使mAlwaysInTapRegion为true
final int deltaX=(int) (focusX-mDownFocusX);
final int deltaY=(int) (focusY-mDownFocusY);
int distance=(deltaX*deltaX)+(deltaY*deltaY);
if (distance > mTouchSlopSquare) { //距离超过一个数值,可以认为是滑动,进入Scroll事件
handled = mListener.onScroll( mCurrentDownEvent, ev, scrollX, scrollY);
mLastFocusX = focusX;
mLastFocusY = focusY;
mAlwaysInTapRegion = false;
mHandler.removeMessages(TAP);
mHandler.removeMessages( SHOW_PRESS);
mHandler.removeMessages( LONG_PRESS);
}
if(distance > mDoubleTapTouchSlopSquare){
//如果移动距离超过允许范围,则不再可能认为移动事件是双击
mAlwaysInBiggerTapRegion = false;
}
} else if ((Math.abs(scrollX) >= 1) || (Math.abs(scrollY) >= 1)) {
//后续的Scroll移动,前面的是进入Scroll移动
handled = mListener.onScroll( mCurrentDownEvent, ev, scrollX, scrollY);
mLastFocusX = focusX;
mLastFocusY = focusY;
}
break;
//...
对MOVE事件涉及:
1)onDoubleTapEvent()回调:只要确认是双击之后,mIsDoubleTapping为true,除了长按,后面的MOVE事件都会只回调onDoubleTapEvent()。
2)onScroll()回调:当MOVE不是长按,不是DoubleTapEvent之后,当移动距离大于一定距离之后,就会进入Scroll模式,然后两个MOVE事件的位移距离scrollX或者scrollY大于1px,都会调用onScroll()。
⑤UP事件的处理
case MotionEvent.ACTION_UP:
mStillDown = false;
MotionEvent currentUpEvent = MotionEvent.obtain(ev);
if (mIsDoubleTapping) {
//双击事件
handled |= mDoubleTapListener. onDoubleTapEvent(ev);
} else if (mInLongPress) {
//长按结束
mHandler.removeMessages(TAP);
mInLongPress = false;
} else if (mAlwaysInTapRegion && !mIgnoreNextUpEvent) {
handled = mListener.onSingleTapUp(ev);
//处理单击确认,具体逻辑看GestureHandler如何处理TAP事件
if (mDeferConfirmSingleTap && mDoubleTapListener != null) {
mDoubleTapListener.onSingleTapConf irmed(ev);
}
} else if (!mIgnoreNextUpEvent) {
//处理Fling,如果速度大于定义的最小速度(50),就回调Fling
// A fling must travel the minimum tap distance
final VelocityTracker velocityTracker = mVelocityTracker;
final int pointerId = ev.getPointerId(0);
velocityTracker.computeCurrentVelocity( 1000, mMaximumFlingVelocity);
final float velocityY = velocityTracker.getYVelocity(pointerId);
final float velocityX = velocityTracker.getXVelocity(pointerId);
if ((Math.abs(velocityY) > mMinimumFlingVelocity) || (Math.abs(velocityX) > mMinimumFlingVelocity)){
handled = mListener.onFling( mCurrentDownEvent, ev, velocityX, velocityY);
}
}
//重置mPreviousUpEvent
if (mPreviousUpEvent != null) {
mPreviousUpEvent.recycle();
}
// Hold the event we obtained above - listeners may have changed the original.
mPreviousUpEvent = currentUpEvent;
//回收mVelocityTracker
if (mVelocityTracker != null) {
// This may have been cleared when we called out to the
// application above.
mVelocityTracker.recycle();
mVelocityTracker = null;
}
mIsDoubleTapping = false;
mDeferConfirmSingleTap = false;
mIgnoreNextUpEvent = false;
mHandler.removeMessages(SHOW_PRESS);
mHandler.removeMessages(LONG_PRESS);
break;
//...
UP事件涉及:
1)onSingleTapUp()回调:DOWN事件之后没有MOVE,或者MOVE的距离没有超出范围,mAlwaysInTapRegion才不会变成false,回调onSingleTapUp()。
2)onSingleTapConfirmed()回调:从前面GestureHandler里面的TAP消息的实现可以看到:
case TAP:
//这里控制SingleTapConfirmed的回调
if (mDoubleTapListener != null) {
if (!mStillDown) {
//如果已经松开,就立刻调用SingleTapConfirmed
mDoubleTapListener.onSingleTapConfi rmed( mCurrentDownEvent);
} else {
//如果处理Message的时候还没松开,就设置mDeferConfirmSingleTap为true,在UP事件的时候调用SingleTapConfirme
mDeferConfirmSingleTap = true;
}
}
break;
之前看过,TAP消息是延时(300ms)发送的,然而实际逻辑中,是抬起手指才算是点击,所以这里处理TAP的时候就不一定立刻调用onSingleTapConfirmed(),而是判断手指是否松开了,是松开的话就立刻回调。如果还未松开那就把标志位mDeferConfirmSingleTap设置为true,等到收到UP事件的时候再回调。
3)onFling()回调:当UP事件的速度大于一定速度时,就会回调onFling(),至于mIgnoreNextUpEvent参数,是只有鼠标右键点击的时候才会为true。
⑥多点触摸的处理
对于多个手指落下,前面的统一处理已经是把所有手指的坐标值加起来然后算平均值了,所以多手指触摸的时候,其实滑动的实际点是这些手指的中心焦点。回看上面的MOVE事件处理,中心焦点的位置值FocusX和FocusY决定onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY)的后两个参数值,所以处理多点触控的话就使用它们比较方便。因为MotionEvent是使用数组装着当前屏幕上所有指针的动作的,使用前两个参数的话还要循环用getX(int pointerIndex)和getY(int pointerIndex)方法取出各个指针的值再自己处理。
case MotionEvent.ACTION_POINTER_DOWN:
mDownFocusX = mLastFocusX = focusX;
mDownFocusY = mLastFocusY = focusY;
//如果有多根手指按下,取消长按和点击计时
cancelTaps();
break;
case MotionEvent.ACTION_POINTER_UP:
mDownFocusX = mLastFocusX = focusX;
mDownFocusY = mLastFocusY = focusY;
//计算每一秒钟的滑动像素
mVelocityTracker.computeCurrentVelocity( 1000, mMaximumFlingVelocity);
final int upIndex = ev.getActionIndex();
final int id1 = ev.getPointerId(upIndex);
final float x1 = mVelocityTracker. getXVelocity(id1);
final float y1 = mVelocityTracker. getYVelocity(id1);
//如果剩下的手指速度方向是和抬起那根手指的速度相反方向的,就说明不是fling,清空速度监听
for (int i = 0; i < count; i++) {
if (i == upIndex) continue;
final int id2 = ev.getPointerId(i);
final float x = x1 * mVelocityTracker. getXVelocity(id2);
final float y = y1 * mVelocityTracker. getYVelocity(id2);
final float dot = x + y;
if (dot < 0) {
mVelocityTracker.clear();
break;
}
}
break;
//...
private void cancelTaps() {
mHandler.removeMessages(SHOW_PRESS);
mHandler.removeMessages(LONG_PRESS);
mHandler.removeMessages(TAP);
mIsDoubleTapping = false;
mAlwaysInTapRegion = false;
mAlwaysInBiggerTapRegion = false;
mDeferConfirmSingleTap = false;
mInLongPress = false;
mInContextClick = false;
mIgnoreNextUpEvent = false;
}
这是对多个手指的UP和DOWN事件处理,其实就是做一些取消操作而让多点触摸不影响单点触摸的应用,例如在多个手指落下的时候取消点击信息等。
⑦ContextClick的处理
ContextClick的事件是由onGenericMotionEvent()传入:
public boolean onGenericMotionEvent( MotionEvent ev) {
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier. onGenericMotionEvent(ev, 0);
}
final int actionButton = ev.getActionButton();
switch (ev.getActionMasked()) {
case MotionEvent.ACTION_BUTTON_PRESS:
//按下触控笔首选按钮或者鼠标右键
if (mContextClickListener != null && !mInContextClick && !mInLongPress && (actionButton == MotionEvent.BUTTON_STYLUS_PRIMARY || actionButton == MotionEvent.BUTTON_SECONDARY)) {
if (mContextClickListener. onContextClick(ev)) {
mInContextClick = true;
mHandler.removeMessages( LONG_PRESS);
mHandler.removeMessages(TAP);
return true;
}
}
break;
case MotionEvent.ACTION_BUTTON_RELEASE:
if (mInContextClick && (actionButton == MotionEvent.BUTTON_STYLUS_PRIMARY || actionButton == MotionEvent.BUTTON_SECONDARY)) {
mInContextClick = false;
//无视下一个UP事件,因为它是由鼠标右键或者触控笔键带起的
mIgnoreNextUpEvent = true;
}
break;
}
return false;
}
由此可以,当按键按下(ACTION_BUTTON_PRESS)的时候已经回调onContextClick()。
4.ScaleGestureDetector
ScaleGestureDetector是用于处理缩放的工具类,用法与GestureDetector类似,都是通过onTouchEvent()关联相应的MotionEvent事件。
ScaleGestureDetector类提供了OnScaleGestureListener接口,处理缩放过程中的一些回调。
public interface OnScaleGestureListener {
//缩放进行中,返回值表示是否下次缩放需要重置,如果返回ture,那么detector就会重置缩放事件,如果返回false,detector会在之前的缩放上继续进行计算
public boolean onScale( ScaleGestureDetector detector);
//缩放开始,返回值表示是否受理后续的缩放事件
public boolean onScaleBegin( ScaleGestureDetector detector);
//缩放结束
public void onScaleEnd(ScaleGestureDetector detector);
}
①onScaleBegin
当一个缩放手势开始时触发该事件,即双指按下屏幕时。该方法的返回值为boolean,主要用来判断探测器是否应该继续识别处理这个手势。例如,如果在屏幕上的某个区域内不需要识别缩放手势,当手势的焦点在该区域内时,onScaleBegin()可以返回false忽略不处理该手势的接下来的一系列事件。
②onScale
当一个缩放手势正在进行中时持续触发该事件,即双指正在屏幕上滑动。该方法的返回值为boolean,主要用来判断是否结束当前缩放手势事件,即本次缩放事件是否已被处理。如果已被处理,那么detector就会重置缩放事件;如果未被处理,detector会继续进行计算,修改getScaleFactor()的返回值,直到被处理为止。当返回true时,则结束当前缩放手势的一系列事件,缩放因子将会初始化为1.0,当返回false时,当前缩放手势的一系列事件继续进行,缩放因子不会被初始化,而是根据手势不断的持续变化。返回值可以用来限制缩放值的最大比例上限和最小比例下限。
③onScaleEnd
当一个缩放手势结束后触发该事件,即双指抬起离开屏幕时。
在缩放过程中,要通过ScaleGestureDetector来获取一些缩放信息。ScaleGestureDetector类提供了常用函数:
public boolean isInProgress(); //缩放是否正处在进行中
public float getFocusX(); //返回组成缩放手势(两个手指)中点x的位置
public float getFocusY(); //返回组成缩放手势(两个手指)中点y的位置
public float getCurrentSpan();组成缩放手势的两个触点的跨度(两个触点间的距离)
public float getCurrentSpanX(); //同上,x的距离
public float getCurrentSpanY(); //同上,y的距离
public float getPreviousSpan();组成缩放手势的两个触点的前一次缩放的跨度(两个触点间的距离)
public float getPreviousSpanX(); //同上,x的距离
public float getPreviousSpanY(); //同上,y的距离
public float getScaleFactor(); //获取本次缩放事件的缩放因子,缩放事件以onScale()返回值为基准,一旦该方法返回true,代表本次事件结束,重新开启下次缩放事件。
getScaleFactor返回从之前的缩放手势和当前的缩放手势两者的比例因子,即缩放值,默认1.0。当手指做缩小操作时,该值小于1.0,当手指做放大操作时,该值大于1.0。在每一个缩放事件中都会从1.0开始变化,如果需要做持续性操作,则需要保存上一次的伸缩值,然后当前的伸缩值*上一次的伸缩值,得到连续变化的伸缩值,例如有3次事件发生,没连续性则是缩小0.9->缩小0.9->缩小0.9,如果做了连续性处理即保存上一次的伸缩值,则是缩小0.9->缩小0.9*0.9=0.81->缩小0.81*0.9=0.72,有了逐渐变小的连续性。
public long getTimeDelta(); //返回上次缩放事件结束时到当前的时间间隔
public long getEventTime(); //获取当前motion事件的时间
5.ScaleGestureDetector使用
ScaleGestureDetector的使用很简单:
①定义ScaleGestureDetector类。
②将touch事件交给Scale GestureDetector,即在onTouchEvent函数里面调用ScaleGestureDetector的onTouchEvent函数。
③处理OnScaleGestureListener的各个回调。
注意:
①重写的三个回调方法里,如果在onScaleBegin里返回false,则永远不会回调onScale方法。
②如果onScaleBegin返回true,onScale返回false,则一次缩放手势里会回调一次onScaleBegin,回调多次onScale。
6.ScaleGestureDetector源码分析
ScaleGestureDetector的源码比GestureDetector的源码要稍微复杂点,因为ScaleGestureDetector的事件涉及到多个手指。
想要缩放的值,所有的MotionEvent事件都要交给ScaleGestureDetector的onTouchEvent()函数,所以直接来看onTouchEvent()函数大概的逻辑。
public boolean onTouchEvent(MotionEvent event) {
...
// 缩放的手势需要多个手指来完成,count表示 手指的个数
final int count = event.getPointerCount();
...
// streamComplete表示当前事件流是否完成
final boolean streamComplete = action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL || anchoredScaleCancelled;
if (action == MotionEvent.ACTION_DOWN || streamComplete) {
// mInProgress表示是否进行缩放,这里是停掉上一次的缩放调用onScaleEnd()
if (mInProgress) {
mListener.onScaleEnd(this);
mInProgress = false;
mInitialSpan = 0;
mAnchoredScaleMode = ANCHORED_SCALE_MODE_NONE;
} else if (inAnchoredScaleMode() && streamComplete) {
mInProgress = false;
mInitialSpan = 0;
mAnchoredScaleMode = ANCHORED_SCALE_MODE_NONE;
}
if (streamComplete) {
return true;
}
}
...
if (inAnchoredScaleMode()) {
...
} else {
// 所有手指的距离相加
for (int i = 0; i < count; i++) {
if (skipIndex == i) continue;
sumX += event.getX(i);
sumY += event.getY(i);
}
// 所有手指的中心点
focusX = sumX / div;
focusY = sumY / div;
}
// Determine average deviation from focal point
float devSumX = 0, devSumY = 0;
for (int i = 0; i < count; i++) {
if (skipIndex == i) continue;
// 所有手指相对于中心点(所有手指的中心点)的距离之和
devSumX += Math.abs(event.getX(i) - focusX);
devSumY += Math.abs(event.getY(i) - focusY);
}
// 所有手指相对于中心点的平均值
final float devX = devSumX / div;
final float devY = devSumY / div;
// *2 相当于是两个手指之间的距离跨度
final float spanX = devX * 2;
final float spanY = devY * 2;
final float span;
if (inAnchoredScaleMode()) {
span = spanY;
} else {
span = (float) Math.hypot(spanX, spanY);
}
...
final int minSpan = inAnchoredScaleMode() ? mSpanSlop : mMinSpan;
// 回调onScaleBegin(),返回值表示是否开始缩放
if (!mInProgress && span >= minSpan &&(wasInProgress || Math.abs(span - mInitialSpan) > mSpanSlop)) {
mPrevSpanX = mCurrSpanX = spanX;
mPrevSpanY = mCurrSpanY = spanY;
mPrevSpan = mCurrSpan = span;
mPrevTime = mCurrTime;
mInProgress = mListener.onScaleBegin(this);
}
// 回调onScale(),如果onScale()返回true,则重新保存mPrevSpanX,mPrevSpanY,mPrevSpan,mPrevTime
if (action == MotionEvent.ACTION_MOVE) {
mCurrSpanX = spanX;
mCurrSpanY = spanY;
mCurrSpan = span;
boolean updatePrev = true;
if (mInProgress) {
updatePrev = mListener.onScale(this);
}
if (updatePrev) {
mPrevSpanX = mCurrSpanX;
mPrevSpanY = mCurrSpanY;
mPrevSpan = mCurrSpan;
mPrevTime = mCurrTime;
}
}
return true;
}
onTouchEvent()函数里面,要注意onScale()的返回值为true的时候mPrevSpanX,mPrevSpanY,mPrevSpan,mPrevTime这些才会改变。
再看下缩放过程中的缩放因子是怎么计算到的。getScaleFactor()函数:
public float getScaleFactor() {
...
return mPrevSpan > 0 ? mCurrSpan / mPrevSpan : 1;
}
用当前两个手指之间的跨度除以上一次记录的两个手指之间的跨度。同时也注意到上面讲到的onTouchEvent()函数里面onScale()返回true的时候mPrevSpan才会重新赋值。比如两个手指放在屏幕上,手指慢慢的拉开。假设回调过程中onScale()函数每次返回的是true,每次onScale()之后getScaleFactor()会重新去计算缩放因子,但是如果onScale()函数返回的是false,getScaleFactor()的返回值是一直增大的。
7.同时使用ScaleGestureDetector.SimpleOnScaleGestureListener和GestureDetector.SimpleOnGestureListener
@Override
public boolean onTouchEvent(MotionEvent event) {
boolean res = mScaleGestureDetector.onTouchEvent(event);
if (!mScaleGestureDetector.isInProgress()) {
res = mGestureDetector.onTouchEvent(event);
}
return res;
}
思路就是首先判断是不是在缩放过程中,如果不是缩放,才去执行mGestureDetector.onTouchEvent(event);