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输出:

android remote进程是否会复制主进程 android remote control_Math

从图中可以看出,在第二下点击时,先触发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);