說說Android上的事件傳遞

我是綠色大米呀發表於2017-08-11

之前寫過一篇事件傳遞相關的部落格

一句話總結一個事件從父View到子View的傳遞:

dispatchTouchEvent是告訴你的領導(父VIew)這件事是不是交給你來做,返回true,交給你;返回false,這事我不管……你去找別人。無論這件事歸不歸你管,領導問你能不能做的時候,你總要說句話吧?所以當一個ViewGroup或者View接受到事件時dispatchTouchEvent總會被呼叫。onInterceptTouchEvent是告訴你的手下的小弟(該View的子View)剛才領導交的差事是你親自做,還是小弟們做,你親自做返回true,然後呼叫你自己的onTouchEvent,返回false,交給小弟做。

上面說的是一個完整的事件,是從 down->move->move->up 整個事件。

這次說說OnTouchListener和onClickListener。
在View.java->dispatchTouchEvent(MotionEvent event)方法中,有這樣一段程式碼

if (onFilterTouchEventForSecurity(event)) {
            if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
                result = true;
            }
            //noinspection SimplifiableIfStatement
            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;
            }
        }複製程式碼

如果設定了OnTouchListener,這裡就會呼叫OnTouchListener的onTouch方法,並且返回true(告訴父View,這個事件我來處理)。
如果沒有設定OnTouchListener,那麼往下看,呼叫了onTouchEvent(event),如果返回true,dispatchTouchEvent就返回true。

從這裡可以知道,OnTouchListener.onTouch是優先於onTouchEvent(event)的。如果OnTouchListener.onTouch返回true(消耗了事件),那麼result就等於true,後面if (!result && onTouchEvent(event))中的onTouchEvent(event)就不會走了。而我們常用的onClickListener就是在onTouchEvent(event)中被呼叫的。所以onTouch返回true,onClick就不會被執行的原因就在這裡。

根據上面的程式碼發現,onClick要被執行是需要很多步判斷,可謂是歷經千難萬阻。但是實際的程式設計中,我們只要給View新增一個onClickListener就行了,這是怎麼做到的?請往下看

在onTouchEvent(MotionEvent event)的程式碼的MotionEvent.ACTION_UP的事件下,有這樣一段程式碼

if (((viewFlags & CLICKABLE) == CLICKABLE ||
                (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
                (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {
            switch (action) {
                case MotionEvent.ACTION_UP:
                .....
                if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
                            // 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();
                                }
                            }
                        }
                        ...
                        return true;
    }複製程式碼

這裡可以發現為什麼onClick和onLongClick不會衝突。
這裡呼叫了performClick(),在這之前做了一些執行緒安全的工作。onclick()就是在performClick()中被執行的。

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;
    }複製程式碼

通過程式碼發現,一定要滿足if (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) 這個條件,這裡才會被呼叫。而且onTouchEvent會返回true。

而當我們繫結監聽器的時候,這個View會被設成clickable = true

public void setOnClickListener(@Nullable OnClickListener l) {
        if (!isClickable()) {
            setClickable(true);
        }
        getListenerInfo().mOnClickListener = l;
    }複製程式碼

這就是“我們只要給View新增一個onClickListener就行了”的原因了。繫結了監聽器,onTouchEvent會返回true,dispatchTouchEvent就返回true。

相關文章