之前寫過一篇事件傳遞相關的部落格。
一句話總結一個事件從父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。