魅族手機onTouchEvent 、onLongClick踩坑

jayqiu發表於2018-08-30

在遇到的坑之前我們先了解 Android onTouchEvent, onClick及onLongClick的呼叫機制 1.  onTouchEvent:

 onTouchEvent中要處理的最常用的3個事件就是:ACTION_DOWN(按下)、ACTION_MOVE(移動)、ACTION_UP(抬起)。
複製程式碼

2.  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值,這裡有兩種判斷: 1. 返回true,表示該View接受此按下動作,就是說這個點選動作的按下操作被中止,然後就是響應ACTION_UP事件。點選動作的按下操作被ACTION_DOWN接受之後就結束了,所以之後的OnClick/OnLongClick事件就不會響應了。 2. 返回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);
		}
	});
複製程式碼

Star 我的GitHub

相關文章