触摸事件
参考:
- onTouchevent() vs onTouch()
- 安卓自定义View进阶-MotionEvent详解
- 触摸事件【MotionEvent】简介
- ACTION_CANCEL,ACTION_OUTSIDE 的触发条件与使用
- 事件处理之onTouchEvent()和onTouch()方法精炼详解
- 3.1.3 MotionEvent和TouchSlop
- Android手势检测——GestureDetector全面分析
文章目录
以下列出的代码有删减!
一、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_MODAL
和FLAG_WATCH_OUTSIDE_TOUCH
。这个flag可以用来标记出现在window
区域外的触摸。在Dialog
中有一个方法setCanceledOnTouchOutside
,这里就有用到ACTION_OUTSIDE的概念。如果在Window
中设置了FLAG_NOT_TOUCH_MODAL
和FLAG_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_DOWN和ACTION_POINTER_UP的。因为getAction
获取到的ACTION是一个合成的数据,包含两部分:pointerId
和ACTION_POINTER_DOWN。其中pointerId
是动态变化的,所以会造成无法进入ACTION_POINTER_DOWN分支。
- getAction:
MotionEvent.getAction()
会返回一个32位的int值(0x00000000),低8位(0x000000ff)表示事件类型,倒数低8位(0x0000ff00)表示事件编号。ACTION_DOWN = 0,ACTION_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
。例如,想要获取x和y。如果直接用getX()
和getY()
而不传入pointerId
,那么返回的值就是第一次触屏的x和y。所以可以使用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
的状态是ENABLE。OnTouchListener.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;
}
}
<!-- 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
,源码中都是在post
或postDelayed
中调用它们的。post
和postDelayed
最大的不同是后者延迟某个特定的时间再执行。
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.post
或View.postDelayed
的目的是让这个View
先更新完其他可见状态,然后再执行单击/长按。
三、GestureDetector
GestureDetector
是一个封装了手势监听
的类。它用于手势处理。
1. GestureDetector和onTouchEvent的区别
GestureDetector
和onTouchEvent
的区别:
- 它不是
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
中最核心的两个接口是OnGestureListener
和OnDoubleTapListener
,最核心的内部类是SimpleOnGestureListener
。
根据官方的说法,如果只是想重写OnGestureListener
或OnDoubleTapListener
中的某一些方法,那么可以直接继承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中监听双击事件的,而View
在ACTION_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_DOWN、ACTION_MOVE、ACTION_UP都会触发。
onDoubleTap
先于onDoubleTapEvent
触发。
由于双击事件在监听第二次点击的时候会执行mHandler.removeMessage(TAP)
,所以如果当前是双击事件,那么这个消息不会被处理。
onDoubleTap
只在第二次单击的ACTION_DOWN时执行,onDoubleTapEvent
只在第二次单击的ACTION_DOWN、ACTION_MOVE、ACTION_UP时执行。
- isConsideredDoubleTap
①当
mAlwaysInBiggerTapRegion
为FALSE时,也就是事件被取消、单击被取消、第一次点击移动距离过大,不视为双击。
mAlwaysInBiggerTapRegion
在ACTION_DOWN中会置为TRUE,不过是判断完单双击之后执行的。mAlwaysInBiggerTapRegion
在cancel
、cancelTaps
中置为FALSE。在ACTION_MOVE中,如果确定了第一次点击,手指还没有抬起时移动的偏移距离超过mDoubleTapTouchSlopSquare
,mAlwaysInBiggerTapRegion
置为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 = null
和mStillDown = 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消息时mStillDown
为TRUE,所以mDeferConfirmingSingleTap
(延迟确认单击)置为TRUE。因为抬起手指的是时候,整个单击的持续时间没有到LONGPRESS_TIMEOUT,那么在ACTION_UP的时候就要处理成单击。
- onSingleTapUp
onSingleTapUp
是普通的单击,也就是说,每一次ACTION_UP都会触发。
确定是单击之后,只在ACTION_UP调用。
4. onShowPress
每一次ACTION_DOWN都会触发,而且ACTION_MOVE或ACTION_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_UP和ACTION_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。