在遇到的坑之前我們先了解 Android onTouchEvent, onClick及onLongClick的調用機制
-
onTouchEvent:
onTouchEvent中要處理的最常用的3個事件就是:ACTION_DOWN(按下)、ACTION_MOVE(移動)、ACTION_UP(抬起)。
-
onClick、onLongClick與onTouchEvent
在Android中,onClick、onLongClick的觸發是和ACTION_DOWN及ACTION_UP相關的,在時序上,如果我們在一個View中同時覆寫了onClick、onLongClick及onTouchEvent的話,onTouchEvent是最先捕捉到ACTION_DOWN和ACTION_UP事件的,其次才可能觸發onClick或者onLongClick。主要的邏輯在View.java中的onTouchEvent方法中實現的,主要的操作是在View.onTucchEvent下的:
switch (event.getAction()) { case MotionEvent.ACTION_UP: boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0; if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) { // take focus if we don't have it already and we should in // touch mode. boolean focusTaken = false; if (isFocusable() && isFocusableInTouchMode() && !isFocused()) { focusTaken = requestFocus(); } if (prepressed) { // The button is being released before we actually // showed it as pressed. Make it show the pressed // state now (before scheduling the click) to ensure // the user sees it. setPressed(true, x, y); } 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(); } } } if (mUnsetPressedState == null) { mUnsetPressedState = new UnsetPressedState(); } if (prepressed) { postDelayed(mUnsetPressedState, ViewConfiguration.getPressedStateDuration()); } else if (!post(mUnsetPressedState)) { // If the post failed, unpress right now mUnsetPressedState.run(); } removeTapCallback(); } break; case MotionEvent.ACTION_DOWN: mHasPerformedLongPress = false; if (performButtonActionOnTouchDown(event)) { break; } // Walk up the hierarchy to determine if we're inside a scrolling container. boolean isInScrollingContainer = isInScrollingContainer(); // For views inside a scrolling container, delay the pressed feedback for // a short period in case this is a scroll. if (isInScrollingContainer) { mPrivateFlags |= PFLAG_PREPRESSED; if (mPendingCheckForTap == null) { mPendingCheckForTap = new CheckForTap(); } mPendingCheckForTap.x = event.getX(); mPendingCheckForTap.y = event.getY(); postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout()); } else { // Not inside a scrolling container, so show the feedback right away setPressed(true, x, y); checkForLongClick(0); } break; case MotionEvent.ACTION_CANCEL: setPressed(false); removeTapCallback(); removeLongPressCallback(); break; case MotionEvent.ACTION_MOVE: drawableHotspotChanged(x, y); // Be lenient about moving outside of buttons if (!pointInView(x, y, mTouchSlop)) { // Outside button removeTapCallback(); if ((mPrivateFlags & PFLAG_PRESSED) != 0) { // Remove any future long press/tap checks removeLongPressCallback(); setPressed(false); } } break;
可以看到,Click的觸發是在系統捕捉到ACTION_UP后發生并由performClick()執行的;LongClick的觸發則是從ACTION_DOWN開始,由checkForLongClick()方法完成的;
他們執行Touch事件的的順序 ACTION_DOWN -> ACTION_UP -> OnClick/OnLongClick。
首先,該View會先響應ACTION_DOWN事件,并返回一個boolean值,這里有兩種判斷:
返回true,表示該View接受此按下動作,就是說這個點擊動作的按下操作被中止,然后就是響應ACTION_UP事件。點擊動作的按下操作被ACTION_DOWN接受之后就結束了,所以之后的OnClick/OnLongClick事件就不會響應了。
-
返回false,表示該View不接受此按下動作,響應完之后,按下操作繼續往下發,之后是響應ACTION_UP事件,這里又有一個判斷:
如果ACTION_UP事件返回true,表示ACTION_UP接受松開操作,松開操作中止;View會一直處于按下狀態,之后View便會響應OnLongClick事件。
如果ACTION_UP事件返回false,表示ACTION_UP不接收松開操作,松開操作繼續下發;因為按下與松開操作都沒有被中止,所以之后View就會響應OnClick事件。
下面就是我在魅族中踩的坑了:
手機在EditText長按進行粘貼,會觸發OnLongClick 的事件,但是我在魅族的手機EditText長按進行粘貼,如果做了onTouchEvent監聽,死活的都不會觸發OnLongClick 的事件,不夠onTouchEvent返回的是true還是false 都白費,當然也不會進行粘貼;其他的手機都是按照正常的節奏來,就只有魅族!
找到問題了現在就是怎么去解決問題,這里我用到onTouchEvent 主要監聽的是ACTION_MOVE 的處理,既然onTuchEvent不行就只有用其方式來監聽滑動了,用到的是GestureDetector.SimpleOnGestureListener 進行監聽的 :
public static class SimpleOnGestureListener implements OnGestureListener, OnDoubleTapListener {
// --------------OnGestureListener的接口有這幾個----------------
// 抬起,手指離開觸摸屏時觸發(長按、滾動、滑動時,不會觸發這個手勢)
public boolean onSingleTapUp(MotionEvent e) {
return false;
}
// 長按,觸摸屏按下后既不抬起也不移動,過一段時間后觸發
public void onLongPress(MotionEvent e) {
}
// 滾動,觸摸屏按下后移動
public boolean onScroll(MotionEvent e1, MotionEvent e2,
float distanceX, float distanceY) {
return false;
}
// 滑動,觸摸屏按下后快速移動并抬起,會先觸發滾動手勢,跟著觸發一個滑動手勢
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
float velocityY) {
return false;
}
// 短按,觸摸屏按下后片刻后抬起,會觸發這個手勢,如果迅速抬起則不會
public void onShowPress(MotionEvent e) {
}
// 單擊,觸摸屏按下時立刻觸發
public boolean onDown(MotionEvent e) {
return false;
}
//------------------------OnDoubleTapListener的接口有這幾個-----------
// 雙擊,手指在觸摸屏上迅速點擊第二下時觸發
public boolean onDoubleTap(MotionEvent e) {
return false;
}
// 雙擊的按下跟抬起各觸發一次
public boolean onDoubleTapEvent(MotionEvent e) {
return false;
}
// 單擊確認,即很快的按下并抬起,但并不連續點擊第二下
public boolean onSingleTapConfirmed(MotionEvent e) {
return false;
}
}
于是我重寫我的EditText 繼承EditText:
public class MyEditText extends EditText {
private GestureDetector mGestureDetector;
private SimpleOnGestureListener simpleOnGestureListener;
private Context context;
public MyEditText(Context context) {
super(context);
this.context = context;
setLongClickable(true);
}
public void setMyGestureListener(SimpleOnGestureListener simpleOnGestureListener) {
this.simpleOnGestureListener = simpleOnGestureListener;
setGestureDetector();
}
public MyEditText(Context context, AttributeSet attrs) {
super(context, attrs);
setLongClickable(true);
this.context = context;
}
private void setGestureDetector() {
mGestureDetector = new GestureDetector(context, simpleOnGestureListener);
this.setOnTouchListener(new OnTouchListener() {
public boolean onTouch(View v, MotionEvent event) {
return mGestureDetector.onTouchEvent(event);
}
});
}
}
注意:
1.這里一定要記得設置longClickable為true, 否則手勢識別無法正確工作,只會返回Down, Show, Long三種手勢
2. 必須在View的onTouchListener中調用手勢識別,而不能像Activity一樣重載onTouchEvent,否則同樣手勢識別無法正確工作
最后這樣就ok了:
mTvEditContent.setMyGestureListener(new SimpleOnGestureListener() {
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
// 處理我想做的
return super.onScroll(e1, e2, distanceX, distanceY);
}
});