天天看點

關于android事件分發的總結

今天在工作中涉及到touch事件分發,花了不少的時間,為了以後少走彎路,今天特将android中事件的分發總結一下。

一. 關于View的事件分發:

為了講解更多的明晰明了,特舉一個例子。界面中有一個button,分别注冊OnClickListener和OnTouchListener事件,先看看執行過程。

<span style="white-space:pre">		</span>btn.setOnClickListener(new OnClickListener() {
			
			@Override
			public void onClick(View v) {
				Log.d(tag, "onclick");
			}
		});
		
		btn.setOnTouchListener(new OnTouchListener() {
			
			@Override
			public boolean onTouch(View v, MotionEvent event) {
				Log.d(tag, "onTouch action = " + event.getAction());
				return false;
			}
		});
           

我們點選一下按鈕,看輸出結果:

06-03 22:34:26.368: D/MainActivity(16796): onTouch action = 0
06-03 22:34:26.398: D/MainActivity(16796): onTouch action = 2
06-03 22:34:26.428: D/MainActivity(16796): onTouch action = 2
06-03 22:34:26.448: D/MainActivity(16796): onTouch action = 2
06-03 22:34:26.448: D/MainActivity(16796): onTouch action = 2
06-03 22:34:26.448: D/MainActivity(16796): onTouch action = 1
06-03 22:34:26.458: D/MainActivity(16796): onclick
           

可以看出,先執行了touch的down,move,up,然後在執行onclick單擊事件。這對于我們有一定的android開發經驗的人來說,很容易了解的。我們不妨将onTouch的傳回值改為true試試。請看輸出結果:

06-03 22:37:35.708: D/MainActivity(19029): onTouch action = 0
06-03 22:37:35.718: D/MainActivity(19029): onTouch action = 2
06-03 22:37:35.758: D/MainActivity(19029): onTouch action = 2
06-03 22:37:35.808: D/MainActivity(19029): onTouch action = 2
06-03 22:37:35.818: D/MainActivity(19029): onTouch action = 2
06-03 22:37:35.828: D/MainActivity(19029): onTouch action = 1
           

這個時候,隻是執行ontouch事件,并沒有執行onclick。這是為啥呢。我們目前可以簡單的了解為ontouch傳回true,将事件消費了。我們都知道我們在手機螢幕上觸摸了,就會執行相應的view的dispatchTouchEvent方法。我們不妨去看看源碼吧。

public boolean dispatchTouchEvent(MotionEvent event) {
        if (!onFilterTouchEventForSecurity(event)) {
            return false;
        }

        if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
                mOnTouchListener.onTouch(this, event)) {
            return true;
        }
        return onTouchEvent(event);
    }
           

mOnTouchListener就是我們通過代碼setOnTouchListener設定進去的。(mViewFlags & ENABLED_MASK) == ENABLED 表示目前的view是不是enable的,一般這個表達式的結果都為true。第三部分就是ontouch方法的傳回值了,如果傳回true。那麼整個方法就直接傳回true。

我們剛剛在ontouch方法将傳回值設定為true了。那麼說明此時button的dispatchTouchEvent直接傳回true。如果在ontouch方法中傳回false,才會執行onTouchEvent方法。那麼我們可以肯定的是onclick方法是在onTouchEvent方法中被觸發執行的。

下邊我們不妨來看看onTouchEvent的源碼吧。

public boolean onTouchEvent(MotionEvent event) {
        final int viewFlags = mViewFlags;

        if ((viewFlags & ENABLED_MASK) == DISABLED) {
            // A disabled view that is clickable still consumes the touch
            // events, it just doesn't respond to them.
            return (((viewFlags & CLICKABLE) == CLICKABLE ||
                    (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));
        }

        if (mTouchDelegate != null) {
            if (mTouchDelegate.onTouchEvent(event)) {
                return true;
            }
        }

        if (((viewFlags & CLICKABLE) == CLICKABLE ||
                (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
            switch (event.getAction()) {
                case MotionEvent.ACTION_UP:
                    boolean prepressed = (mPrivateFlags & PREPRESSED) != 0;
                    if ((mPrivateFlags & 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 (!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) {
                            mPrivateFlags |= PRESSED;
                            refreshDrawableState();
                            postDelayed(mUnsetPressedState,
                                    ViewConfiguration.getPressedStateDuration());
                        } else if (!post(mUnsetPressedState)) {
                            // If the post failed, unpress right now
                            mUnsetPressedState.run();
                        }
                        removeTapCallback();
                    }
                    break;

                case MotionEvent.ACTION_DOWN:
                    if (mPendingCheckForTap == null) {
                        mPendingCheckForTap = new CheckForTap();
                    }
                    mPrivateFlags |= PREPRESSED;
                    mHasPerformedLongPress = false;
                    postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
                    break;

                case MotionEvent.ACTION_CANCEL:
                    mPrivateFlags &= ~PRESSED;
                    refreshDrawableState();
                    removeTapCallback();
                    break;

                case MotionEvent.ACTION_MOVE:
                    final int x = (int) event.getX();
                    final int y = (int) event.getY();

                    // Be lenient about moving outside of buttons
                    int slop = mTouchSlop;
                    if ((x < 0 - slop) || (x >= getWidth() + slop) ||
                            (y < 0 - slop) || (y >= getHeight() + slop)) {
                        // Outside button
                        removeTapCallback();
                        if ((mPrivateFlags & PRESSED) != 0) {
                            // Remove any future long press/tap checks
                            removeLongPressCallback();

                            // Need to switch from pressed to not pressed
                            mPrivateFlags &= ~PRESSED;
                            refreshDrawableState();
                        }
                    }
                    break;
            }
            return true;
        }

        return false;
    }
           

代碼比較長。我們不妨挑選重點來看吧。看17行代碼。 if (((viewFlags & CLICKABLE) == CLICKABLE ||  (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE))隻要目前控件是可以被點選的,不管事單擊,還是長按,就進入switch内部。我們可以看到43行代碼performClick(),我們看看源碼吧。

public boolean performClick() {
        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);

        if (mOnClickListener != null) {
            playSoundEffect(SoundEffectConstants.CLICK);
            mOnClickListener.onClick(this);
            return true;
        }

        return false;
    }
           

mOnClickListener就是我們通過setOnClickListener設定進去的。這時,我們就應該知道我們的單擊事件其實需要依賴ontouch的。現在我們就能解釋,為什麼我們将ontouch傳回值設定為true以後,onclick就不執行的原因了,就是因為onclick是在ontouchevent方法中執行的,而ontouch傳回true以後,ontouchevent方法不執行了,最終導緻onclick不執行。

touch事件的傳遞和執行其實是要有條件的。比如,down的傳回了false時,move、up是不執行的。那有人就會有疑問,那剛剛ontouch傳回false了,up、move事件照樣執行呀。是的,當ontouch傳回false以後,就去執行ontouchevent,又由于button是可點選的,導緻在ontouchevent源碼中,總能執行到101行代碼,傳回true了,因而可以傳遞up和move了。

有些控件預設不具有可點選性,ontouch傳回false的話,可能導緻有down事件,沒有後續up和move事件,這時候解決的辦法有兩個,一個傳回true,一個設定clickable

二. 總結一下view事件的傳遞要點。

1. view事件的傳遞也是從dispatchTouchEvent開始的。

2. ontouch方法要比ontouchevent方法先執行。當ontouch傳回true時,ontouchevent不會執行了。view的一些常見的click和longclick事件都市在ontouchevent方法中被觸發的。

3. 對應view而言,隻有上一個touch傳回true時,下一個事件(move,up)才會被觸發。

今天的總結到此為止,後續将總結viewgroup事件的分發,還有就是在listview、scrollview中某一個item的手勢和listview、scrollview本身有沖突如何解決的。