google 提供的API中,有個類,大家都很熟悉,GestureDetector。使用它,我們可以識别使用者通常會用的手勢。但是,這個類不支援多點觸摸(可能 google認為沒有人會在幾個手指都在螢幕上的時候,使用手勢吧~),不過,最近和朋友們一起做的一個App,的确用到了多點手勢(主要是 onScroll和onFling兩個手勢),是以,我就把這個類拓展了一下,來實作讓多個控件各自跟着一跟手指實作拖動和滑動的效果。
順便說一下,大家應該都知道,在Android3.0以後,Android的觸摸事件的配置設定機制和以前的版本是有差別的。從3.0開始,使用者在不同控件
上操作産生的touch消息不會互相幹擾,touch消息會被分派到不同控件上的touchListener中處理。而
在以前的版本中,所有的touch消息,都會被分排到第一個碰到螢幕的手指所操作的控件的touchListener中處理,也就是說,會出現這樣一個沖突的現象:
在界面上有A,B,C三個控件,然後,當你先用食指按住A,跟着又用中指和無名指(嘛,别的手指也行,不用在意中指還是無名指)按住B,C。當中指和無
名指移動的時候,B和C都無法接收到這個ACTION_MOVE消息,而接收到消息的卻是A。而在3.0以上版本中,并不存在這個問題。
使用以下的這個類,可以實作從2.2到3.2平台上手勢識别的相容。
代碼區:
packagecom.finger.utils;
importjava.util.ArrayList;
importjava.util.List;
importandroid.content.Context;
importandroid.os.Handler;
importandroid.os.Message;
importandroid.view.MotionEvent;
importandroid.view.VelocityTracker;
importandroid.view.ViewConfiguration;
publicclassMultiTouchGestureDetector {
@SuppressWarnings("unused")
privatestaticfinalString MYTAG ="Ray";
publicstaticfinalString CLASS_NAME ="MultiTouchGestureDetector";
/**
* 事件資訊類 <br/>
* 用來記錄一個手勢
*/
privateclassEventInfo {
privateMultiMotionEvent mCurrentDownEvent; //目前的down事件
privateMultiMotionEvent mPreviousUpEvent; //上一次up事件
privatebooleanmStillDown; //目前手指是否還在螢幕上
privatebooleanmInLongPress; //目前事件是否屬于長按手勢
privatebooleanmAlwaysInTapRegion; //是否目前手指僅在小範圍内移動,當手指僅在小範圍内移動時,視為手指未曾移動過,不會觸發onScroll手勢
privatebooleanmAlwaysInBiggerTapRegion; //是否目前手指在較大範圍内移動,僅當此值為true時,輕按兩下手勢才能成立
privatebooleanmIsDoubleTapping; //目前手勢,是否為輕按兩下手勢
privatefloatmLastMotionY; //最後一次事件的X坐标
privatefloatmLastMotionX; //最後一次事件的Y坐标
privateEventInfo(MotionEvent e) {
this(newMultiMotionEvent(e));
}
privateEventInfo(MultiMotionEvent me) {
mCurrentDownEvent = me;
mStillDown =true;
mInLongPress =false;
mAlwaysInTapRegion =true;
mAlwaysInBiggerTapRegion =true;
mIsDoubleTapping =false;
//釋放MotionEven對象,使系統能夠繼續使用它們
publicvoidrecycle() {
if(mCurrentDownEvent !=null) {
mCurrentDownEvent.recycle();
mCurrentDownEvent =null;
}
if(mPreviousUpEvent !=null) {
mPreviousUpEvent.recycle();
mPreviousUpEvent =null;
@Override
publicvoidfinalize() {
this.recycle();
}
* 多點事件類 <br/>
* 将一個多點事件拆分為多個單點事件,并友善獲得事件的絕對坐标
* <br/> 絕對坐标用以在界面中找到觸點所在的控件
* @author ray-ni
publicclassMultiMotionEvent {
privateMotionEvent mEvent;
privateintmIndex;
privateMultiMotionEvent(MotionEvent e) {
mEvent = e;
mIndex = (e.getAction() &
MotionEvent.ACTION_POINTER_ID_MASK) >>
MotionEvent.ACTION_POINTER_ID_SHIFT; //等效于 mEvent.getActionIndex();
privateMultiMotionEvent(MotionEvent e,intidx) {
mIndex = idx;
// 行為
publicintgetAction() {
intaction = mEvent.getAction() & MotionEvent.ACTION_MASK; //等效于 mEvent.getActionMasked();
switch(action) {
caseMotionEvent.ACTION_POINTER_DOWN:
action = MotionEvent.ACTION_DOWN;
break;
caseMotionEvent.ACTION_POINTER_UP:
action = MotionEvent.ACTION_UP;
returnaction;
// 傳回X的絕對坐标
publicfloatgetX() {
returnmEvent.getX(mIndex) + mEvent.getRawX() - mEvent.getX();
// 傳回Y的絕對坐标
publicfloatgetY() {
returnmEvent.getY(mIndex) + mEvent.getRawY() - mEvent.getY();
// 事件發生的時間
publiclonggetEventTime() {
returnmEvent.getEventTime();
// 事件序号
publicintgetIndex() {
returnmIndex;
// 事件ID
publicintgetId() {
returnmEvent.getPointerId(mIndex);
// 釋放事件對象,使系統能夠繼續使用
if(mEvent !=null) {
mEvent.recycle();
mEvent =null;
// 多點手勢監聽器
publicinterfaceMultiTouchGestureListener {
// 手指觸碰到螢幕,由一個 ACTION_DOWN觸發
booleanonDown(MultiMotionEvent e);
// 确定一個press事件,強調手指按下的一段時間(TAP_TIMEOUT)内,手指未曾移動或擡起
voidonShowPress(MultiMotionEvent e);
// 手指點選螢幕後離開,由 ACTION_UP引發,可以簡單的了解為單擊事件,即手指點選時間不長(未構成長按事件),也不曾移動過
booleanonSingleTapUp(MultiMotionEvent e);
// 長按,手指點下後一段時間(DOUBLE_TAP_TIMEOUT)内,不曾擡起或移動
voidonLongPress(MultiMotionEvent e);
// 拖動,由ACTION_MOVE觸發,手指地按下後,在螢幕上移動
booleanonScroll(MultiMotionEvent e1, MultiMotionEvent e2,floatdistanceX,floatdistanceY);
// 滑動,由ACTION_UP觸發,手指按下并移動一段距離後,擡起時觸發
booleanonFling(MultiMotionEvent e1, MultiMotionEvent e2,floatvelocityX,floatvelocityY);
// 多點輕按兩下監聽器
publicinterfaceMultiTouchDoubleTapListener {
// 單擊事件确認,強調第一個單擊事件發生後,一段時間内,未發生第二次單擊事件,即确定不會觸發輕按兩下事件
booleanonSingleTapConfirmed(MultiMotionEvent e);
// 輕按兩下事件, 由ACTION_DOWN觸發,從第一次單擊事件的DOWN事件開始的一段時間(DOUBLE_TAP_TIMEOUT)内結束(即手指),
// 并且在第一次單擊事件的UP時間開始後的一段時間内(DOUBLE_TAP_TIMEOUT)發生第二次單擊事件,
// 除此之外兩者坐标間距小于定值(DOUBLE_TAP_SLAP)時,則觸發輕按兩下事件
booleanonDoubleTap(MultiMotionEvent e);
// 輕按兩下事件,與onDoubleTap事件不同之處在于,構成輕按兩下的第二次點選的ACTION_DOWN,ACTION_MOVE和ACTION_UP都會觸發該事件
booleanonDoubleTapEvent(MultiMotionEvent e);
// 事件資訊隊列,隊列的下标與MotionEvent的pointId對應
privatestaticList<EventInfo> sEventInfos =newArrayList<EventInfo>(10);
// 輕按兩下判斷隊列,這個隊列中的元素等待輕按兩下逾時的判斷結果
privatestaticList<EventInfo> sEventForDoubleTap =newArrayList<EventInfo>(5);
// 指定大點選區域的大小(這個比較拗口),這個值主要用于幫助判斷輕按兩下是否成立
privateintmBiggerTouchSlopSquare =20*20;
// 判斷是否構成onScroll手勢,當手指在這個範圍内移動時,不觸發onScroll手勢
privateintmTouchSlopSquare;
// 判斷是否構成輕按兩下,隻有兩次點選的距離小于該值,才能構成輕按兩下手勢
privateintmDoubleTapSlopSquare;
// 最小滑動速度
privateintmMinimumFlingVelocity;
// 最大滑動速度
privateintmMaximumFlingVelocity;
// 長按閥值,當手指按下後,在該閥值的時間内,未移動超過mTouchSlopSquare的距離并未擡起,則長按手勢觸發
privatestaticfinalintLONGPRESS_TIMEOUT = ViewConfiguration.getLongPressTimeout();
// showPress手勢的觸發閥值,當手指按下後,在該閥值的時間内,未移動超過mTouchSlopSquare的距離并未擡起,則showPress手勢觸發
privatestaticfinalintTAP_TIMEOUT = ViewConfiguration.getTapTimeout();
// 輕按兩下逾時閥值,僅在兩次輕按兩下事件的間隔(第一次單擊的UP事件和第二次單擊的DOWN事件)小于此閥值,輕按兩下事件才能成立
privatestaticfinalintDOUBLE_TAP_TIMEOUT = ViewConfiguration.getDoubleTapTimeout();
// 輕按兩下區域閥值,僅在兩次輕按兩下事件的距離小于此閥值,輕按兩下事件才能成立
privatestaticfinalintDOUBLE_TAP_SLAP =64;
// GestureHandler所處理的Message的what屬性可能為以下 常量:
// showPress手勢
privatestaticfinalintSHOW_PRESS =1;
// 長按手勢
privatestaticfinalintLONG_PRESS =2;
// SingleTapConfirmed手勢
privatestaticfinalintTAP_SINGLE =3;
// 輕按兩下手勢
privatestaticfinalintTAP_DOUBLE =4;
// 手勢處理器
privatefinalGestureHandler mHandler;
// 手勢監聽器
privatefinalMultiTouchGestureListener mListener;
// 輕按兩下監聽器
privateMultiTouchDoubleTapListener mDoubleTapListener;
// 長按允許閥值
privatebooleanmIsLongpressEnabled;
// 速度追蹤器
privateVelocityTracker mVelocityTracker;
privateclassGestureHandlerextendsHandler {
GestureHandler() {
super();
GestureHandler(Handler handler) {
super(handler.getLooper());
publicvoidhandleMessage(Message msg) {
intidx = (Integer) msg.obj;
switch(msg.what) {
caseSHOW_PRESS: {
if(idx >= sEventInfos.size()) {
// Log.w(MYTAG, CLASS_NAME + ":handleMessage,
msg.what = SHOW_PRESS, idx=" + idx + ", while sEventInfos.size()="
// + sEventInfos.size());
break;
}
EventInfo info = sEventInfos.get(idx);
if(info ==null) {
// Log.e(MYTAG, CLASS_NAME + ":handleMessage, msg.what = SHOW_PRESS, idx=" + idx + ", Info = null");
// 觸發手勢監聽器的onShowPress事件
mListener.onShowPress(info.mCurrentDownEvent);
caseLONG_PRESS: {
// Log.d(MYTAG, CLASS_NAME + ":trigger LONG_PRESS");
msg.what = LONG_PRESS, idx=" + idx + ", while sEventInfos.size()="
// Log.e(MYTAG, CLASS_NAME + ":handleMessage, msg.what = LONG_PRESS, idx=" + idx + ", Info = null");
dispatchLongPress(info, idx);
caseTAP_SINGLE: {
// Log.d(MYTAG, CLASS_NAME + ":trriger TAP_SINGLE");
// If the user's finger is still down, do not count it as a tap
// Log.e(MYTAG, CLASS_NAME + ":handleMessage,
msg.what = TAP_SINGLE, idx=" + idx + ", while sEventInfos.size()="
// Log.e(MYTAG, CLASS_NAME + ":handleMessage, msg.what = TAP_SINGLE, idx=" + idx + ", Info = null");
if(mDoubleTapListener !=null&& !info.mStillDown) {//手指在輕按兩下逾時的閥值内未離開螢幕進行第二次單擊事件,則确定單擊事件成立(不再觸發輕按兩下事件)
mDoubleTapListener.onSingleTapConfirmed(info.mCurrentDownEvent);
caseTAP_DOUBLE: {
if(idx >= sEventForDoubleTap.size()) {
msg.what = TAP_DOUBLE, idx=" + idx + ", while
sEventForDoubleTap.size()="
// + sEventForDoubleTap.size());
EventInfo info = sEventForDoubleTap.get(idx);
// Log.w(MYTAG, CLASS_NAME + ":handleMessage, msg.what = TAP_DOUBLE, idx=" + idx + ", Info = null");
sEventForDoubleTap.set(idx,null);// 這個沒什麼好做的,就是把隊列中對應的元素清除而已
default:
thrownewRuntimeException("Unknown message "+ msg);// never
* 觸發長按事件
* @param info
* @param idx
privatevoiddispatchLongPress(EventInfo info,intidx) {
mHandler.removeMessages(TAP_SINGLE, idx);//移除單擊事件确認
info.mInLongPress =true;
mListener.onLongPress(info.mCurrentDownEvent);
/**
* 構造器1
* @param context
* @param listener
publicMultiTouchGestureDetector(Context context, MultiTouchGestureListener listener) {
this(context, listener,null);
* 構造器2
* @param handler
publicMultiTouchGestureDetector(Context context, MultiTouchGestureListener listener, Handler handler) {
if(handler !=null) {
mHandler =newGestureHandler(handler);
}else{
mHandler =newGestureHandler();
mListener = listener;
if(listenerinstanceofMultiTouchDoubleTapListener) {
setOnDoubleTapListener((MultiTouchDoubleTapListener) listener);
init(context);
* 初始化識别器
privatevoidinit(Context context) {
if(mListener ==null) {
thrownewNullPointerException("OnGestureListener must not be null");
mIsLongpressEnabled =true;
inttouchSlop, doubleTapSlop;
if(context ==null) {
touchSlop = ViewConfiguration.getTouchSlop();
doubleTapSlop = DOUBLE_TAP_SLAP;
mMinimumFlingVelocity = ViewConfiguration.getMinimumFlingVelocity();
mMaximumFlingVelocity = ViewConfiguration.getMaximumFlingVelocity();
}else{//允許識别器在App中,使用偏好的設定
finalViewConfiguration configuration = ViewConfiguration.get(context);
touchSlop = configuration.getScaledTouchSlop();
doubleTapSlop = configuration.getScaledDoubleTapSlop();
mMinimumFlingVelocity = configuration.getScaledMinimumFlingVelocity();
mMaximumFlingVelocity = configuration.getScaledMaximumFlingVelocity();
mTouchSlopSquare = touchSlop * touchSlop /16;
mDoubleTapSlopSquare = doubleTapSlop * doubleTapSlop;
* 設定輕按兩下監聽器
* @param onDoubleTapListener
publicvoidsetOnDoubleTapListener(MultiTouchDoubleTapListener onDoubleTapListener) {
mDoubleTapListener = onDoubleTapListener;
* 設定是否允許長按
* @param isLongpressEnabled
publicvoidsetIsLongpressEnabled(booleanisLongpressEnabled) {
mIsLongpressEnabled = isLongpressEnabled;
* 判斷是否允許長按
* @return
publicbooleanisLongpressEnabled() {
returnmIsLongpressEnabled;
* 判斷目前事件是否為輕按兩下事件
* <br/> 通過周遊sEventForDoubleTap來比對是否存在能夠構成輕按兩下事件的單擊事件
* @param e
privateEventInfo checkForDoubleTap(MultiMotionEvent e) {
if(sEventForDoubleTap.isEmpty()) {
// Log.e(MYTAG, CLASS_NAME + ":checkForDoubleTap(), sEventForDoubleTap is empty !");
returnnull;
for(inti =0; i < sEventForDoubleTap.size(); i++) {
EventInfo info = sEventForDoubleTap.get(i);
if(info !=null&& isConsideredDoubleTap(info, e)) {
sEventForDoubleTap.set(i,null);// 這個單擊事件已經被消耗了,是以置為null
mHandler.removeMessages(TAP_DOUBLE, i);// 移除Handler内的為處理消息
returninfo;
returnnull;
* 判斷目前按下事件是否能和指定的單擊事件構成輕按兩下事件
*
* @param secondDown
privatebooleanisConsideredDoubleTap(EventInfo info, MultiMotionEvent secondDown) {
if(!info.mAlwaysInBiggerTapRegion) {//如多第一次單擊事件有過較大距離的移動,則無法構成輕按兩下事件
returnfalse;
if(secondDown.getEventTime() - info.mPreviousUpEvent.getEventTime() > DOUBLE_TAP_TIMEOUT) {
//如果第一次單擊的UP時間和第二次單擊的down時間時間間隔大于DOUBLE_TAP_TIMEOUT,也無法構成輕按兩下事件
intdeltaX = (int) info.mCurrentDownEvent.getX() - (int) secondDown.getX();
intdeltaY = (int) info.mCurrentDownEvent.getY() - (int) secondDown.getY();
return(deltaX * deltaX + deltaY * deltaY < mDoubleTapSlopSquare);//最後判斷兩次單擊事件的距離
* 将事件資訊放入輕按兩下判斷隊列,并傳回序号
privateintaddIntoTheMinIndex(EventInfo info) {
if(sEventForDoubleTap.get(i) ==null) {
sEventForDoubleTap.set(i, info);
returni;
sEventForDoubleTap.add(info);
returnsEventForDoubleTap.size() -1;
* 從事件資訊隊列中移除指定序号的事件
privatevoidremoveEventFromList(intid) {
if(id > sEventInfos.size() || id <0) {
// Log.e(MYTAG, CLASS_NAME + ".removeEventFromList(), id=" +
id + ", while sEventInfos.size() =" + sEventInfos.size());
return;
sEventInfos.set(id,null);
* 向事件隊列中添加新資訊
privatevoidaddEventIntoList(EventInfo info) {
intid = info.mCurrentDownEvent.getId();
if(id < sEventInfos.size()) {
// if (sEventInfos.get(id) != null)
// Log.e(MYTAG, CLASS_NAME + ".addEventIntoList, info(" + id + ") has not set to null !");
sEventInfos.set(info.mCurrentDownEvent.getId(), info);
}elseif(id == sEventInfos.size()) {
sEventInfos.add(info);
// Log.e(MYTAG, CLASS_NAME + ".addEventIntoList, invalidata id !");
publicbooleanonTouchEvent(MotionEvent ev) {
if(mVelocityTracker ==null) {
mVelocityTracker = VelocityTracker.obtain();
mVelocityTracker.addMovement(ev);//把所有事件都添加到速度追蹤器,為計算速度做準備
booleanhandled =false;
finalintaction = ev.getAction();//擷取Action
// int idx = (action & MotionEvent.ACTION_POINTER_INDEX_MASK)
>> MotionEvent.ACTION_POINTER_INDEX_SHIFT;//擷取觸摸事件的序号
intidx = ev.getPointerId(ev.getActionIndex());//擷取觸摸事件的id
switch(action & MotionEvent.ACTION_MASK) {
caseMotionEvent.ACTION_DOWN:
caseMotionEvent.ACTION_POINTER_DOWN: {
EventInfo info =newEventInfo(MotionEvent.obtain(ev));
this.addEventIntoList(info);//将手勢資訊儲存到隊列中
if(mDoubleTapListener !=null) {//如果輕按兩下監聽器不為null
if(mHandler.hasMessages(TAP_DOUBLE)) {
MultiMotionEvent e =newMultiMotionEvent(ev);
EventInfo origInfo = checkForDoubleTap(e);//檢查是否構成輕按兩下事件
if(origInfo !=null) {
info.mIsDoubleTapping =true;
handled |= mDoubleTapListener.onDoubleTap(origInfo.mCurrentDownEvent);
handled |= mDoubleTapListener.onDoubleTapEvent(e);
}
if(!info.mIsDoubleTapping) {//目前事件不構成輕按兩下事件,那麼發送延遲消息以判斷onSingleTapConfirmed事件
mHandler.sendMessageDelayed(mHandler.obtainMessage(TAP_SINGLE, idx), DOUBLE_TAP_TIMEOUT);
// Log.d(MYTAG, CLASS_NAME + ": add TAP_SINGLE");
// 記錄X坐标和Y坐标
info.mLastMotionX = info.mCurrentDownEvent.getX();
info.mLastMotionY = info.mCurrentDownEvent.getY();
if(mIsLongpressEnabled) {//允許長按
mHandler.removeMessages(LONG_PRESS, idx);
mHandler.sendMessageAtTime(mHandler.obtainMessage(LONG_PRESS, idx),
info.mCurrentDownEvent.getEventTime() + TAP_TIMEOUT
+ LONGPRESS_TIMEOUT);//延時消息以觸發長按手勢
// Log.d(MYTAG, CLASS_NAME +
// ":add LONG_PRESS to handler for idx " + idx);
mHandler.sendMessageAtTime(mHandler.obtainMessage(SHOW_PRESS, idx),
info.mCurrentDownEvent.getEventTime() + TAP_TIMEOUT);//
延時消息,觸發showPress手勢
handled |= mListener.onDown(info.mCurrentDownEvent);//觸發onDown()
break;
caseMotionEvent.ACTION_UP:
caseMotionEvent.ACTION_POINTER_UP: {
MultiMotionEvent currentUpEvent =newMultiMotionEvent(ev);
if(idx >= sEventInfos.size()) {
// Log.e(MYTAG, CLASS_NAME + ":ACTION_POINTER_UP, idx=" +
idx + ", while sEventInfos.size()=" + sEventInfos.size());
EventInfo info = sEventInfos.get(currentUpEvent.getId());
if(info ==null) {
// Log.e(MYTAG, CLASS_NAME + ":ACTION_POINTER_UP, idx=" + idx + ", Info = null");
info.mStillDown =false;
if(info.mIsDoubleTapping) {//處于輕按兩下狀态,則觸發onDoubleTapEvent事件
handled |= mDoubleTapListener.onDoubleTapEvent(currentUpEvent);
}elseif(info.mInLongPress) {//處于長按狀态
mHandler.removeMessages(TAP_SINGLE, idx);//可以無視這行代碼
info.mInLongPress =false;
}elseif(info.mAlwaysInTapRegion) {//尚未移動過
if(mHandler.hasMessages(TAP_SINGLE, idx)) {//還在輕按兩下的時間閥值内,是以要為輕按兩下判斷做額外處理
mHandler.removeMessages(TAP_SINGLE, idx);
info.mPreviousUpEvent =newMultiMotionEvent(MotionEvent.obtain(ev));
intindex =this.addIntoTheMinIndex(info);// 把目前事件放入隊列,等待輕按兩下的判斷
mHandler.sendMessageAtTime(mHandler.obtainMessage(TAP_DOUBLE, index), info.mCurrentDownEvent.getEventTime()
+ DOUBLE_TAP_TIMEOUT);// 将輕按兩下逾時判斷添加到Handler
// Log.d(MYTAG, CLASS_NAME + ": add TAP_DOUBLE");
handled = mListener.onSingleTapUp(currentUpEvent);//觸發onSingleTapUp事件
}else{
// A fling must travel the minimum tap distance
finalVelocityTracker velocityTracker = mVelocityTracker;
velocityTracker.computeCurrentVelocity(1000, mMaximumFlingVelocity);//計算1秒鐘内的滑動速度
//擷取X和Y方向的速度
finalfloatvelocityX = velocityTracker.getXVelocity(idx);
finalfloatvelocityY = velocityTracker.getYVelocity(idx);
// Log.i(MYTAG, CLASS_NAME + ":ACTION_POINTER_UP, idx=" + idx +
// ", vx=" + velocityX + ", vy=" + velocityY);
// 觸發滑動事件
if((Math.abs(velocityY) > mMinimumFlingVelocity) || (Math.abs(velocityX) > mMinimumFlingVelocity)) {
handled = mListener.onFling(info.mCurrentDownEvent, currentUpEvent, velocityX, velocityY);
// Hold the event we obtained above - listeners may have changed the
// original.
if(action == MotionEvent.ACTION_UP) { //釋放速度追蹤器
mVelocityTracker.recycle();
mVelocityTracker =null;
// Log.w(MYTAG, CLASS_NAME +
// ":ACTION_POINTER_UP, mVelocityTracker.recycle()");
info.mIsDoubleTapping =false;
// Log.d(MYTAG, CLASS_NAME + "remove LONG_PRESS");
// 移除showPress和長按消息
mHandler.removeMessages(SHOW_PRESS, idx);
mHandler.removeMessages(LONG_PRESS, idx);
removeEventFromList(currentUpEvent.getId());//手指離開,則從隊列中删除手勢資訊
caseMotionEvent.ACTION_MOVE:
for(intrIdx =0; rIdx < ev.getPointerCount(); rIdx++) {//因為無法确定目前發生移動的是哪個手指,是以周遊處理所有手指
MultiMotionEvent e =newMultiMotionEvent(ev, rIdx);
if(e.getId() >= sEventInfos.size()) {
// Log.e(MYTAG, CLASS_NAME + ":ACTION_MOVE, idx=" +
rIdx + ", while sEventInfos.size()=" + sEventInfos.size());
EventInfo info = sEventInfos.get(e.getId());
// Log.e(MYTAG, CLASS_NAME + ":ACTION_MOVE, idx=" + rIdx + ", Info = null");
if(info.mInLongPress) { //長按,則不處理move事件
//目前坐标
floatx = e.getX();
floaty = e.getY();
//距離上次事件移動的位置
finalfloatscrollX = x - info.mLastMotionX;
finalfloatscrollY = y - info.mLastMotionY;
if(info.mIsDoubleTapping) {//輕按兩下事件
handled |= mDoubleTapListener.onDoubleTapEvent(e);
}elseif(info.mAlwaysInTapRegion) {//該手勢尚未移動過(移動的距離小于mTouchSlopSquare,視為未移動過)
// 計算從落下到目前事件,移動的距離
finalintdeltaX = (int) (x - info.mCurrentDownEvent.getX());
finalintdeltaY = (int) (y - info.mCurrentDownEvent.getY());
// Log.d(MYTAG, CLASS_NAME + "deltaX="+deltaX+";deltaY=" +
// deltaX +"mTouchSlopSquare=" + mTouchSlopSquare);
intdistance = (deltaX * deltaX) + (deltaY * deltaY);
if(distance > mTouchSlopSquare) { // 移動距離超過mTouchSlopSquare
handled = mListener.onScroll(info.mCurrentDownEvent, e, scrollX, scrollY);
info.mLastMotionX = e.getX();
info.mLastMotionY = e.getY();
info.mAlwaysInTapRegion =false;
// Log.d(MYTAG, CLASS_NAME +
// ":remove LONG_PRESS for idx" + rIdx +
// ",mTouchSlopSquare("+mTouchSlopSquare+"), distance("+distance+")");
// 清除onSingleTapConform,showPress,longPress三種消息
intid = e.getId();
mHandler.removeMessages(TAP_SINGLE, id);
mHandler.removeMessages(SHOW_PRESS, id);
mHandler.removeMessages(LONG_PRESS, id);
if(distance > mBiggerTouchSlopSquare) {//移動距離大于mBiggerTouchSlopSquare,則無法構成輕按兩下事件
info.mAlwaysInBiggerTapRegion =false;
}elseif((Math.abs(scrollX) >=1) || (Math.abs(scrollY) >=1)) {//之前已經移動過了
handled = mListener.onScroll(info.mCurrentDownEvent, e, scrollX, scrollY);
info.mLastMotionX = x;
info.mLastMotionY = y;
caseMotionEvent.ACTION_CANCEL:
cancel();//清理
returnhandled;
// 清理所有隊列
privatevoidcancel() {
mHandler.removeMessages(SHOW_PRESS);
mHandler.removeMessages(LONG_PRESS);
mHandler.removeMessages(TAP_SINGLE);
mVelocityTracker.recycle();
mVelocityTracker =null;
sEventInfos.clear();
sEventForDoubleTap.clear();
}