onTouch和onClick 的那些事兒

原文首發於微信公衆號:躬行之(jzman-blog),歡迎關注交流!

事件的分發流程可以說基本上已經闡述清楚,在閱讀本篇文章之前,請先閱讀下面幾篇文章:

還有一個問題是 Android 事件傳遞過程中 onTouch 和 onClick 事件在整個事件過程中是如何進行事件傳遞的,下面主要是關於 onTouch 、 onClick 與事件傳遞過程中調用的先後順序,將從如下幾個方面介紹:

  1. 源碼中的 onTouch() 方法
  2. 源碼中的 onClick() 方法
  3. onTouch() 與 onClick() 方法之間的關係
  4. 總結

源碼中的onTouch()方法

當要設置觸摸事件的監聽時,使用到 View 類中的 OnTouchListener 接口,然後通過 setOnClickListener 設置對觸摸事件的監聽,然後就可以通過具體的事件類型去執行某些操作,onTouch() 方法就是接口 OnTouchListener 中定義的方法,在 View 的 dispatchTouchEvent() 方法中調用,下面時 onTouch方法在源碼中的具體調用:

//事件分發
public boolean dispatchTouchEvent(MotionEvent event) {
    ...
    //默認返回值
    boolean result = false;
    ...
    //注意判斷條件
    if (onFilterTouchEventForSecurity(event)) {
        if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
            result = true;
        }
        
        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;
        }
    }
   ...
    return result;
}

上述代碼中,先來看一下最外面的條件 onFilterTouchEventForSecurity() 方法,只關心該方法的返回值即可,源碼如下:

/**
 * 如果事件正常分發返回true,如果事件被丟棄返回false
 * @see #getFilterTouchesWhenObscured
 */
public boolean onFilterTouchEventForSecurity(MotionEvent event) {
    //noinspection RedundantIfStatement
    if ((mViewFlags & FILTER_TOUCHES_WHEN_OBSCURED) != 0
            && (event.getFlags() & MotionEvent.FLAG_WINDOW_IS_OBSCURED) != 0) {
        // Window is obscured, drop this touch.
        return false;
    }
    return true;
}

所以該方法正常情況下返回 true,然後關鍵的條件主要就是 ListenerInfo 是否爲 null,mOnTouchListener 是否爲 null,以及 onTouch() 方法的返回值,下面是 ListenerInfo 初始化的源碼部分,具體如下:

//getListenerInfo()的具體調用
public void setOnTouchListener(OnTouchListener l) {
    getListenerInfo().mOnTouchListener = l;
}
//ListenerInfo的初始化
ListenerInfo getListenerInfo() {
    if (mListenerInfo != null) {
        return mListenerInfo;
    }
    mListenerInfo = new ListenerInfo();
    return mListenerInfo;
}

顯然,當通過 setOnTouchListener() 方法設置觸摸事件的監聽時就初始化了 ListenerInfo,同在在設置觸摸事件監聽的時候 mOnTouchListener != null 成立,最後 onTouch() 方法的返回值決定了 dispatchTouchEvent() 方法是否返回 true。所以,當設置了觸摸監聽事件且 onTouch() 方法返回 true 時,表示事件就此處理也就不再向子 View 傳遞了,同時,onTouchEvent() 方法也就不再執行,返回 false 則 onTouchEvent() 方法還會執行。

源碼中的onClick()方法

當要設置點擊事件的事件監聽時,使用到 View 類中的 OnClickListener 接口,然後通過 setOnClickListener 設置對單擊事件的監聽,然後就可以通過具體的事件類型去執行某些操作,onClick() 方法就是 OnClickListener 接口中定義的方法,在 View 的 onTouchEvent() 方法中調用,在下文中也會進一步得到驗證,下面是 onClick() 方法在源碼中的具體調用:

//事件處理
public boolean onTouchEvent(MotionEvent event) {
    ...
    if (((viewFlags & CLICKABLE) == CLICKABLE ||
            (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
            (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {
        switch (action) {
            case MotionEvent.ACTION_UP:
                ...
                //如果執行了該方法,其返回值就是onTouchEvent()的返回值
                performClick();
                ...
                break;
        }
        return true;
    }
    return false;
}

上面代碼中至少找到了 onClick() 方法的調用位置,下面是 performClick() 方法:

/**
 * 主要回調了 OnClickListener
 */
public boolean performClick() {
    final boolean result;
    final ListenerInfo li = mListenerInfo;
    if (li != null && li.mOnClickListener != null) {
        playSoundEffect(SoundEffectConstants.CLICK);
        //調用了OnClickListener接口中的onClick()方法
        li.mOnClickListener.onClick(this);
        result = true;
    } else {
        result = false;
    }
    sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
    return result;
}

顯然,只要我們在代碼中通過 setOnClickListener() 方法設置了對單擊事件的監聽,則對應 View 的 onTouchEvent() 方法返回 true,當然事件就此消費,反之返回 false,那麼 onTouch 與 onClick 之間的調用順序如何,它們之間會互相影響嗎,下面就會從案列的角度瞭解它們之間的關係。

onTouch()與onClick()方法之間的關係

還是之前的案例,MLinearLayout 嵌套 MRelativeLayout,MRelativeLayout 嵌套 MTextView,三個 View 都只是重寫了與它們自身相關的事件分發,然後爲 MTextView 設置對觸摸事件、單擊事件的監聽,具體如下:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    findViewById(R.id.textView).setOnTouchListener(new View.OnTouchListener() {
        @Override
        public boolean onTouch(View v, MotionEvent event) {
            Log.i("Event", "TextView-------onTouch---------------return:" + false);
            return false;
        }
    });

    findViewById(R.id.textView).setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            Log.i("Event", "TextView-------onClick");
    }
    });
}
  1. 讓 onTouch() 返回 false,查看日誌如下:

結論:設置了對觸摸事件的監聽,onTouch() 方法 false 時 onTouchEvent() 方法在 onTouch() 方法之後執行,事件就此消費,接着接受 ACTION_DOWN 之後的一系列事件,途中使用鼠標,故沒有 ACTION_MOVE 事件,還有在 onTouch() 方法返回 false 的情況下 onClick() 執行了。

  1. 讓 onTouch() 返回 true,查看日誌如下:

結論 :當 onTouch() 返回 true 的時候,正如前面所述 onTouchEvent() 將不會再執行,故 onClick() 也就不會再執行。

總結

onTouch() 方法的返回值決定了 onTouchEvent() 方法要不要執行,如果 onTouch() 返回 true,則 onTouchEvent() 不會再執行,返回 false ,則 onTouchEvent() 繼續執行,而 onClick() 的回調是在 onTouchEvent() 方法中調用,onTouchEvent() 不執行則 onClick() 不執行。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章