note_45:手势检测

24 篇文章 0 订阅

触摸事件

参考:



以下列出的代码有删减!


一、MotionEvent

1. ACTION分类

(1) 单点触控
事件说明
ACTION_DOWN手指第一次接触到屏幕时触发
ACTION_MOVE手指在屏幕上滑动时触发,每滑动一个特定的距离就会触发一次
ACTION_UP手指离开屏幕时触发
ACTION_CANCEL事件被上层拦截时触发
ACTION_OUTSIDE手指第一次触摸到控件边界外时触发
  • ACTION_CANCEL:

如果父view拦截了事件的话,子view会收到ACTION_CANCEL

  • ACTION_OUTSIDE:

WindowManager.LayoutParams中有两个flag跟它直接相关——FLAG_NOT_TOUCH_MODALFLAG_WATCH_OUTSIDE_TOUCH。这个flag可以用来标记出现在window区域外的触摸。在Dialog中有一个方法setCanceledOnTouchOutside,这里就有用到ACTION_OUTSIDE的概念。如果在Window中设置了FLAG_NOT_TOUCH_MODALFLAG_WATCH_OUTSIDE_TOUCH,那么第一个down事件就会改成ACTION_OUTSIDE传给子view

(2) 多点触控
事件说明
ACTION_POINTER_DOWN在有其他手指在屏幕上的前提下,另一只手指触碰屏幕
ACTION_POINTER_UP在有其他手指在屏幕上的前提下,另一只手指离开屏幕

主要是针对多只手指触碰屏幕的情况。

public boolean onTouchEvent(MotionEvent event) {
	switch (event.getAction()) {
		case ACTION_DOWN:
			Log.d(TAG, "ACTION_DOWN");
			break;
			
		case ACTION_MOVE:
        	Log.d(TAG, "ACTION_MOVE");
        	break;
        	
        case ACTION_UP:
        	Log.d(TAG, "ACTION_UP");
        	break;
        	
        case ACTION_POINTER_DOWN:
        	Log.d(TAG, "ACTION_POINTER_DOWN");
        	break;
        	
        case ACTION_POINTER_UP:
        	Log.d(TAG, "ACTION_POINTER_UP");
        	break;
        	
        default:
        	break;
	}
}

上述代码中,由于switch-case的条件是event.getAction(),所以是没有办法筛选出条件ACTION_POINTER_DOWNACTION_POINTER_UP的。因为getAction获取到的ACTION是一个合成的数据,包含两部分:pointerIdACTION_POINTER_DOWN。其中pointerId是动态变化的,所以会造成无法进入ACTION_POINTER_DOWN分支。

  • getAction:

MotionEvent.getAction()会返回一个32位的int值(0x00000000),低8位(0x000000ff)表示事件类型,倒数低8位(0x0000ff00)表示事件编号。

ACTION_DOWN = 0ACTION_POINTER_DOWN = 5
第1只手指触屏时,getAction()的返回值为0x00000000,也就是ACTION_DOWN
第2只手指触屏时,getAction()的返回值为0x00000105,也就是1+ACTION_POINTER_DOWN
第3只手指触屏时,getAction()的返回值为0x00000205,也就是2+ACTION_POINTER_DOWN
第4只手指触屏时,getAction()的返回值为0x00000305,也就是3+ACTION_POINTER_DOWN

为了区分出ACTION_POINTER_DOWN,我们需要读取的是低8位,而不是一整个action,所以官方推荐我们用getActionMasked()。我们可以直接使用event.getActionMasked()或者event.getAction() & ACTION_MASK

对于多点触控来说,有时候我们需要获取具体某次某只手指触屏的一些数据,这个时候就需要用上
pointerId。例如,想要获取xy。如果直接用getX()getY()而不传入pointerId,那么返回的值就是第一次触屏的xy。所以可以使用getX(pointerId)getY(pointerId)

(3) 鼠标事件
事件说明
ACTION_HOVER_ENTER鼠标第一次移入View或Window,没有点下
ACTION_HOVER_MOVE鼠标已经在View或Window中移动,没有点下
ACTION_HOVER_EXIT鼠标离开View或Window,没有点下
ACTION_SCROLL鼠标滚轮滚动

上述这四个事件需要在onGenericMotionEvent中获取,onTouchEvent是获取不到的。

2. 输入设备类型

类型说明
TOOL_TYPE_ERASER橡皮擦、手写笔使用末尾
TOOL_TYPE_FINGER手指
TOOL_TYPE_MOUSE鼠标、触控板
TOOL_TYPE_STYLUS手写笔
TOOL_TYPE_UNKNOWN未知设备或非指点设备,如轨迹球、扫描仪

可以通过getToolType(pointerId)来获取设备类型。

getX()getRawX()3.1.3 MotionEvent和TouchSlop


二、View.onTouchEvent和View.OnTouchListener

public boolean dispatchTouchEvent(MotionEvent event) {
    ListenerInfo li = mListenerInfo;
    if (li != null && li.mOnTouchListener != null
        && (mViewFlags & ENABLED_MASK) == ENABLED
        && li.mOnTouchListener.onTouch(this, event)) {
        result = true;
    }

    if (!result && onTouchEvent(event)) {
        result = true;
    }
}

onTouchEvent是在View里面的一个方法,View的派生类可以重写这个方法。它可以用来直接处理View接收到的所有ACTION类型的触摸事件。

OnTouchListener.onTouch在执行之前有条件限制:①ListenerInfo不空;②有绑定OnTouchListener;③当前View的状态是ENABLEOnTouchListener.onTouch成功拦截事件,不让onTouchEvent处理的条件是返回值为TRUE

onTouchEvent相当于通用的方法,如果在某个派生View里面重写了这个方法,那么这个派生View的所有实例都会沿用这个方法。

OnTouchListener.onTouch相当于专用的方法,每个实例都可以动态地绑定不同的OnTouchListener处理触摸事件。

1. TouchSlop

View.java

/**
  * Cache the touch slop from the context that created the view.
  */
private int mTouchSlop;


public View(Context context) {
    mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
}

public boolean onTouchEvent(MotionEvent event) {
    switch (event.getAction()) {
        case MotionEvent.ACTION_MOVE:
            drawHotspotChanged(x, y);
            
            if (!pointInView(x, y, mTouchSlop)) {
                removeTapCallback();
                if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
                    removeLongPressCallback();
                    setPressed(false);
                }
            }
            break;
    }
}

public boolean pointInView(float localX, float localY, float slop) {
    return localX >= -slop && localY >= -slop && localX < ((mRight - mLeft) + slop) &&
        localY < ((mBottom - mTop) + slop);
}

当手指在屏幕上移动时,不是所有移动都会发送ACTION_MOVE。发送的条件是:将当前view四周的边界扩张slop个像素,如果手指当前处在的位置在这个新的边界内,那么就会发送ACTION_MOVE

ViewConfiguration.java

public class ViewConfiguration {
    private final int mTouchSlop;
    
    private ViewConfiguration(Context context) {
        mTouchSlop = res.getDimensionPixelSize(
            com.android.internal.R.dimen.config_viewConfigurationTouchSlop);
    }
    
    public int getScaledTouchSlop() {
        return mTouchSlop;
    }
}

config.xml

<!-- Base "touch slop" value used by ViewConfiguration as a 
	 movement threshold where scrolling should begin. -->
<dimen name="config_viewConfigurationTouchSlop">8dp</dimen>

这个值跟设备有关,不同设备可能不一样。

2. 单击事件和长按事件

单击事件长按事件触发的位置是不同的,单击事件ACTION_UP中判断,而长按事件则是在ACTION_DOWN中判断。不管是单击事件还是长按事件,都要先判断当前状态是否是CLICKABLE或者LONG_CLICKABLE

View.java

private boolean mHasPerformedLongPress;

public boolean onTouchEvent(MotionEvent event) {
    if (((viewFlags & CLICKABLE) == CLICKABLE || 
         (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_UP:
                if (!mHasPerformedLongPress) {
                    // This is a tap, so remove the longpress check
                    removeLongPressCallback();

                    // Only perform take click actions if we were in the pressed state
                    if (!focusTaken) {
                        // Use a Runnable and post this rather than calling
                        // performClick directly. This lets other visual state
                        // of the view update before click actions start.
                        if (mPerformClick == null) {
                            mPerformClick = new PerformClick();
                        }
                        if (!post(mPerformClick)) {
                            performClick();
                        }
                    }
                }
                break;

            case MotionEvent.ACTION_DOWN:
                mHasPerformedLongPress = false;
                // Not inside a scrolling container, so show the feedback right away
                setPressed(true, x, y);
                checkForLongClick(0);
                break;
        }
    }
}

public boolean performClick() {
    final boolean result;
    final ListenerInfo li = mListenerInfo;
    if (li != null && li.mOnClickListener != null) {
        playSoundEffect(SoundEffectConstants.CLICK);
        li.mOnClickListener.onClick(this);
        result = true;
    } else {
        result = false;
    }

    sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
    return result;
}

private void checkForLongClick(int delayOffset) {
    if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) {
        mHasPerformedLongPress = false;

        if (mPendingCheckForLongPress == null) {
            mPendingCheckForLongPress = new CheckForLongPress();
        }
        mPendingCheckForLongPress.rememberWindowAttachCount();
        postDelayed(mPendingCheckForLongPress, 
                    ViewConfiguration.getLongPressTimeout() - delayOffset);
    }
}

private final class CheckForLongPress implements Runnable {
    private int mOriginalWindowAttachCount;

    @Override
    public void run() {
        if (isPressed() && (mParent != null)
            && mOriginalWindowAttachCount == mWindowAttachCount) {
            if (performLongClick()) {
                mHasPerformedLongPress = true;
            }
        }
    }
}

mHasPerformedLongPress这个变量十分关键,它是用来标记当前是否有执行长按的。

判断长按是从ACTION_DOWN就开始了,根据手指触屏的时间是否达到了ViewConfiguration.getLongPressTimeout(),如果是就可以直接执行CheckForLongPress这个Runnable了。当performLongClick返回TRUE后,mHasPerformedLongPress就会标记为TRUE。那么一旦手指抬起时,就不会执行performClick

判断单击是从ACTION_UP才开始,依赖于mHasPerformedLongPress这个状态值。如果手指触屏没达到ViewConfiguration.getLongPressTimeout()就抬起了,那么会直接判断是否是单击事件

不管是performClick还是performLongClick,源码中都是在postpostDelayed中调用它们的。postpostDelayed最大的不同是后者延迟某个特定的时间再执行。

View.java

public boolean post(Runnable action) {
    final AttachInfo attachInfo = mAttachInfo;
    if (attachInfo != null) {
        return attachInfo.mHandler.post(action);
    }
    // Assume that post will succeed later
    ViewRootImpl.getRunQueue().post(action);
    return true;
}

public boolean postDelayed(Runnable action, long delayMillis) {
    final AttachInfo attachInfo = mAttachInfo;
    if (attachInfo != null) {
        return attachInfo.mHandler.postDelayed(action, delayMillis);
    }
    // Assume that post will succeed later
    ViewRootImpl.getRunQueue().postDelayed(action, delayMillis);
    return true;
}

ViewRootImpl.java

static final class RunQueue {
    void post(Runnable action) {
        postDelayed(action, 0);
    }

     void postDelayed(Runnable action, long delayMillis) {
         HandlerAction handlerAction = new HandlerAction();
         handlerAction.action = action;
         handlerAction.delay = delayMillis;

        synchronized (mActions) {
            mActions.add(handlerAction);
        }
     }
}

使用View.postView.postDelayed的目的是让这个View先更新完其他可见状态,然后再执行单击/长按


三、GestureDetector

GestureDetector是一个封装了手势监听的类。它用于手势处理。

1. GestureDetector和onTouchEvent的区别

GestureDetectoronTouchEvent的区别:

  • 它不是View自带的方法,GestureDetector这个类里面包含了手势监听的接口和静态内部类,但是需要创建GestureDetector的实例后手动调用这些方法。但onTouchEvent不需要,一来它是View自带的方法,二来它会在dispatchTouchEvent中被调用。
  • onTouchEvent只能处理长按、单击这种逻辑比较简单的事件,对于手势放大、缩小或者双击这种复杂事件,在onTouchEvent中判断并处理会非常麻烦。但是GestureDetector就可以很完美地解决这个问题,除了可以监听常规的单双击、长按,还能监听滚动和投掷。

GestureDetector.java

public interface OnGestureListener {
    boolean onDown(MotionEvent e);
    
    void onShowPress(MotionEvent e);
    
    boolean onSingleTapUp(MotionEvent e);
    
    boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY);
    
    void onLongPress(MotionEvent e);
    
    boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY);
}

public interface OnDoubleTapListener {
    void onSingleTapConfirmed(MotionEvent e);
    
    boolean onDoubleTap(MotionEvent e);
    
    boolean onDoubleTapEvent(MotionEvent e);
}

public static class SimpleOnGestureListener implements OnGestureListener, OnDoubleTapLitener, OnContextClickListener {
    public boolean onSingleTapUp(MotionEvent e) {
        return false;
    }
    
    public void onLongPress() {
        
    }
    
    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
        return false;
    }
    
    float onFlig(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
        return false;
    }
    
    public void onShowPress(MotionEvent e) {
        
    }
    
    public boolean onDown(MotionEvent e) {
        return false;       
    }
    
    public boolean onDoubleTap(MotionEvent e) {
        return false;
    }
    
    public boolean onDoubleTapEvent(MotionEvent e) {
        return false;
    }
    
    public boolean onSingleTapConfirmed(MotionEvent e) {
        return false;
    }
    
    public boolean onContextClick(MotionEvent e) {
        return false;
    }
}

GestureDetector

GestureDetector中最核心的两个接口是OnGestureListenerOnDoubleTapListener,最核心的内部类是SimpleOnGestureListener

根据官方的说法,如果只是想重写OnGestureListenerOnDoubleTapListener中的某一些方法,那么可以直接继承SimpleOnGestureListener并重写一些方法。

2. 双击

GestureDetector.java

private class GestureHandler extends Handler {
    public void handleMessage(Message msg) {
        switch (msg.what) {      
            case TAP:
                if (mDoubleTapListener != null) {
                    if (!mStillDown) {
                        mDoubleTapListener.onSingleTapConfirmed(mCurrentDownEvent);
                    } else {
                        mDeferConfirmSingleTap = true;
                    }
                }
                break;
        }
    }
}

public boolean onTouchEvent(MotionEvent ev) {
    final int action = ev.getAction();
    
    switch (action & MotionEvent.ACTION_MASK) {
        case MotionEvent.ACTION_DOWN:
            boolean hadTapMessage = mHandler.hasMessage(TAP);
            if (hadTapMessage) mHandler.removeMessage(TAP);
            if ((mCurrentDownEvent != null) && (mPreviousUpEvent != null) && hadTapMessage
               && isConsideredDoubleTap(mCurrentDownEvent, mPreviousUpEvent, ev)) {
                mIsDoubleTapping = true;
                handled |= mDoubleTapListener.onDoubleTap(mCurrentDownEvent);
                handled |= mDoubleTapListener.onDoubleTapEvent(ev);
            } else {
                mHandler.sendEmptyMessageDelayed(TAP, DOUBLE_TAP_TIMEOUT);
            }
            break;
    }
}

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;
}

GestureDetector是在ACTION_DOWN中监听双击事件的,而ViewACTION_UP中监听单击事件

Handler.java

public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
    Message msg = Message.obtain();
    msg.what = what;
    return sendMessageDelayed(msg, delayMillis);
}

public final boolean sendMessageDelayed(Message msg, long delayMillis) {
    if (delayMillis < 0) {
        delayMillis = 0;
    }
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}

public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
    MessageQueue queue = mQueue;
    if (queue == null) {
       RuntimeException e = new RuntimeException(this + " sendMessageAtTime() called with no mQueue");
       Log.w("Looper", e.getMessage(), e);
       return false;
    }
    return enqueueMessage(queue, msg, uptimeMillis);
}

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    msg.target = this;
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}
  • 策略

GestureDetector实现双击事件的策略是:在ACTION_DOWN中监听两次点击,第一次点击的时候GestureHandler发送一个延迟消息,消息内容是TAP,延迟时间是DOUBLE_TAP_TIMEOUT。需要注意的是Handler.sendEmptyMessageDelay是把消息入队了的,消息处在pending状态,过了DOUBLE_TAP_TIMEOUT才会处理。所以才会在ACTION_DOWN的开头判断mHandler.hasMessage(TAP),如果有这个消息,那就证明它的来源是第一次点击;如果没有这个消息,那当前就不是双击的第二次点击。在确定当前是第二次点击的情况下,如果点击区域符合条件,那么就是完整的双击事件了。

  • onDoubleTap、onDoubleTapEvent

onDoubleTap是第二下单击的ACTION_DOWN就会触发。
onDoubleTapEvent是第二下单击的ACTION_DOWNACTION_MOVEACTION_UP都会触发。
onDoubleTap先于onDoubleTapEvent触发。
由于双击事件在监听第二次点击的时候会执行mHandler.removeMessage(TAP),所以如果当前是双击事件,那么这个消息不会被处理。
onDoubleTap只在第二次单击的ACTION_DOWN时执行,onDoubleTapEvent只在第二次单击的ACTION_DOWNACTION_MOVEACTION_UP时执行。

  • isConsideredDoubleTap

①当mAlwaysInBiggerTapRegionFALSE时,也就是事件被取消、单击被取消、第一次点击移动距离过大,不视为双击
mAlwaysInBiggerTapRegionACTION_DOWN中会置为TRUE,不过是判断完单双击之后执行的。mAlwaysInBiggerTapRegioncancelcancelTaps中置为FALSE。在ACTION_MOVE中,如果确定了第一次点击,手指还没有抬起时移动的偏移距离超过mDoubleTapTouchSlopSquaremAlwaysInBiggerTapRegion置为FALSE
②点击事件过长或者过快,不视为双击
③详细计算第二次点击和第一次点击位置之间的面积是否超过mDoubleTapSlopSquare,没超过就是双击,超过就不是双击

GestureDetector.java

    private void cancel() {
        mHandler.removeMessages(SHOW_PRESS);
        mHandler.removeMessages(LONG_PRESS);
        mHandler.removeMessages(TAP);
        mVelocityTracker.recycle();
        mVelocityTracker = null;
        mIsDoubleTapping = false;
        mStillDown = false;
        mAlwaysInTapRegion = false;
        mAlwaysInBiggerTapRegion = false;
        mDeferConfirmSingleTap = false;
        if (mInLongPress) {
            mInLongPress = false;
        }
    }

    private void cancelTaps() {
        mHandler.removeMessages(SHOW_PRESS);
        mHandler.removeMessages(LONG_PRESS);
        mHandler.removeMessages(TAP);
        mIsDoubleTapping = false;
        mAlwaysInTapRegion = false;
        mAlwaysInBiggerTapRegion = false;
        mDeferConfirmSingleTap = false;
        if (mInLongPress) {
            mInLongPress = false;
        }
    }
  • cancel和cancelTaps

cancel()只在ACTION_CANCEL中执行,cancelTaps()只在ACTION_POINTER_DOWN中执行。
这两个方法的代码非常相似,cancel()只比cancelTaps()多了两个赋值:mVelocityTracker = nullmStillDown = false
cancel()的作用是将所有状态置为初始化,把消息队列清空。mVelocityTracker用来跟踪触摸时速度的变化,用于fling事件的判断,所以在ACTION_CANCEL发生的时候,这些跟踪的数据也要清空。mStillDown用来记录双击事件第一次单击的情况,因为现在已经处于ACTION_CANCEL了,所以也不需要了。
cancelTaps()只是用来去掉长按点击scroll事件fling事件还是要继续跟踪的(两只手指滚屏也是滚屏),所以mVelocityTracker不用重置。

3. 单击

GestureDetector.java

private class GestureHandler extends Handler {
    public void handleMessage(Message msg) {
        switch (msg.what) {
            case TAP:
                if (mDoubleTapListener != null) {
                    if (!mStillDown) {
                        mDoubleTapListener.onSingleTapConfirmed(mCurrentDownEvent);
                    } else {
                        mDeferConfirmSingleTap = true;
                    }
                }
                break;
        }
    }
}

public boolean onTouchEvent(MotionEvent ev) {
    final int action = ev.getAction();
    
    switch (action & MotionEvent.ACTION_MASK) {
        case MotionEvent.ACTION_DOWN:
            boolean hadTapMessage = mHandler.hasMessage(TAP);
            if (hadTapMessage) mHandler.removeMessage(TAP);
            if ((mCurrentDownEvent != null) && (mPreviousUpEvent != null) && hadTapMessage
               && isConsiderDoubleTap(mCurrentDownEvent, mPreviousUpEvent, ev)) {
                mIsDoubleTapping = true;
                handled |= mDoubleTapListener.onDoubleTap(mCurrentDownEvent);
                handled |= mDoubleTapListener.onDoubleTapEvent(ev);
            } else {
                mHandler.sendEmptyMessageDelayed(TAP, DOUBLE_TAP_TIMEOUT);
            }
            break;
            
        case MotionEvent.ACTION_UP:
            if (mIsDoubleTapping) {
                handled |= mDoubleTapListener.onDoubleTapEvent(ev);
            } else if (mInLongPress) {
                mHandler.removeMessage(TAP);
                mInLongPress = false;
            } else if (mAlwaysInTapRegion) [
                handled |= mListener.onSingleTapUp(ev);
                if (mDeferConfirmSingleTap && mDoubleTapListener != null) {
                    mDoubleTapListener.onSingleTapConfirmed(ev);
                }
            ]
            break;
    }
}

private void dispatchLongPress() {
    mHandler.removeMessages(TAP);
    mDeferConfirmSingleTap = false;
    mInLongPress = true;
    mListener.onLongPress(mCurrentDownEvent);
}
  • onSingleTapConfirmed

onSingleTapConfirmed是严格的单击,也就是说,确保这个单击不是双击里面的第一下单击。
在整个GestureDetector中,唯一一个发送TAP消息的地方就是第一次单击的时候。所以当需要处理这个消息时,只需要判断是不是严格的单击事件。mStillDown是值有没有抬起手指,也就是有没有走ACTION_UP
①抬起了手指的情况是,单击了第一次之后在DOUBLE_TAP_TIMEOUT之内没有点击第二次且手指已抬起,那么可以视为单击。
②没有抬起手指的情况是,处理TAP消息时mStillDownTRUE,所以mDeferConfirmingSingleTap(延迟确认单击)置为TRUE。因为抬起手指的是时候,整个单击的持续时间没有到LONGPRESS_TIMEOUT,那么在ACTION_UP的时候就要处理成单击。

  • onSingleTapUp
    onSingleTapUp是普通的单击,也就是说,每一次ACTION_UP都会触发。
    确定是单击之后,只在ACTION_UP调用。

4. onShowPress

每一次ACTION_DOWN都会触发,而且ACTION_MOVEACTION_UP还没进行,这个方法是用来提供用户反馈的。

5. 长按

GestureDetector.java

private class GestureHandler extends Handler {
    public void handleMessage(Message msg) {
        switch (msg.what) {
            case LONG_PRESS:
                dispatchLongPress();
                break;
        }
    }
}

public boolean onTouchEvent(MotionEvent ev) {
    final int action = ev.getAction();
    
    switch (action & MotionEvent.ACTION_MASK) {
        case MotionEvent.ACTION_DOWN:
            boolean hadTapMessage = mHandler.hasMessage(TAP);
            if (hadTapMessage) mHandler.removeMessage(TAP);
            if ((mCurrentDownEvent != null) && (mPreviousUpEvent != null) && hadTapMessage
               && isConsiderDoubleTap(mCurrentDownEvent, mPreviousUpEvent, ev)) {
                mIsDoubleTapping = true;
                handled |= mDoubleTapListener.onDoubleTap(mCurrentDownEvent);
                handled |= mDoubleTapListener.onDoubleTapEvent(ev);
            } else {
                mHandler.sendEmptyMessageDelayed(TAP, DOUBLE_TAP_TIMEOUT);
            }
            
            if (mIsLongpressEnabled) {
                mHandler.removeMessages(LONG_PRESS);
                mHandler.sendEmptyMessageAtTime(LONG_PRESS, mCurrentDownEvent.getDownTime() 
                                                + TAP_TIMEOUT + LONGPRESS_TIMEOUT);
            }
            break;
            
        case MotionEvent.ACTION_UP:
            if (mIsDoubleTapping) {
                handled |= mDoubleTapListener.onDoubleTapEvent(ev);
            } else if (mInLongPress) {
                mHandler.removeMessage(TAP);
                mInLongPress = false;
            } else if (mAlwaysInTapRegion) [
                handled |= mListener.onSingleTapUp(ev);
                if (mDeferConfirmSingleTap && mDoubleTapListener != null) {
                    mDoubleTapListener.onSingleTapConfirmed(ev);
                }
            ]
            break;
    }
}

private void dispatchLongPress() {
    mHandler.removeMessages(TAP);
    mDeferConfirmSingleTap = false;
    mInLongPress = true;
    mListener.onLongPress(mCurrentDownEvent);
}
  • onLongPress

长按也是在ACTION_DOWN的时候监听的,它也是发送一个延迟消息,内容为LONG_PRESS,延迟时间为LONGPRESS_TIMEOUT。当超过LONG_PRESS_TIMEOUT之后,GestureHandler会处理LONG_PRESS消息,将mInLongPress置为TRUE,执行mDoubleTapListener.onLongPress。那么在ACTION_UP的时候就不会执行mDoubleTapListener.onSingleTapConfirmed

6. 滚动

GestureDetector.java

public boolean onTouchEvent(MotionEvent ev) {
    final int action = ev.getAction();
    switch (action & MotionEvent.ACTION_MASK) {
        case MotionEvent.ACTION_MOVE:
            if (mIsDoubleTapping) {
                
            } else if (mAlwaysInTapRegion) {
                final int deltaX = (int) (focusX - mDownFocusX);
                final int deltaY = (int) (focusY - mDownFocusY);
                int distance = (deltaX * deltaX) + (deltaY * deltaY);
                if (distance > mTouchSlopSquare) {
                    handled = mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY);
                    mLastFocusX = focusX;
                    mLastFocusY = focusY;
                    mAlwaysInTapRegion = false;
                }
            } else if ((Math.abs(scrollX) >= 1) || (Math.abs(scrollY) >= 1)) {
                handled = mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY);
                mLastFocusX = focusX;
                mLastFocusY = focusY;
            }
            break;
    }
}

只在ACTION_MOVE处理。有以下两种情况:
①只有一个手指在滑动,滑动的范围超过mTouchSlopSquare
②多个手指在滑动,x或y方向上的滑动距离超过1px

7. 投掷(fling)

GestureDetector.java

public boolean onTouchEvent(MotionEvent ev) {
    final int action = ev.getAction();
    switch (action & MotionEvent.ACTION_MASK) {
        case MotionEvent.ACTION_POINTER_UP:
            mDownFocusX = mLastFocusX = focusX;
            mDownFocusY = mLastFocusY = focusY;

            // Check the dot product of current velocities.
            // If the pointer that left was opposing another velocity vector, clear.
            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);
            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;
            
        case MotionEvent.ACTION_UP:
            mStillDown = false;
            MotionEvent currentUpEvent = MotionEvent.obtain(ev);
            if (mIsDoubleTapping) {
                // Finally, give the up event of the double-tap
                handled |= mDoubleTapListener.onDoubleTapEvent(ev);
            } else if (mInLongPress) {
                mHandler.removeMessages(TAP);
                mInLongPress = false;
            } else if (mAlwaysInTapRegion) {
                handled = mListener.onSingleTapUp(ev);
                if (mDeferConfirmSingleTap && mDoubleTapListener != null) {
                    mDoubleTapListener.onSingleTapConfirmed(ev);
                }
            } else {

                // 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);
                }
            }
            break;
    }
}

涉及到ACTION_UPACTION_POINTER_UP,其中ACTION_POINTER_UP仅处理多指触碰时VelocityTracker是否需要重置的问题,真正执行mListener.onFling是在ACTION_UP

  • ACTION_POINTER_UP

可以fling的其中一个条件是抬起的手指的速度方向要跟每个手指的速度方向相同,这样在所有手指抬起之后才能产生惯性的动画效果。

  • ACTION_UP

pointerId = ev.getPointerId(0)就是获取抬起手指的id,最后一根手指离开屏幕。获取这个pointerId就是为了去mVelocityTracker中找到这根手指的速度。只有当前的速度超过fling的最小速度,才可以fling

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值