源碼閱讀,謹記:不要掉坑裏,不要有強迫症,帶着疑惑和問題去找它,閱讀你關心的內容就好,別妄想一次就想搞定它!
事件使用與結論
View.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ALog.dTag("ViewEvent", "自定義View的onClick事件,View:%s", v);
}
});
View.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
ALog.dTag("ViewEvent", "自定義View的onTouch事件,event:%s,View:%s", event, v);
return false;//true-消化此事件,false-不消化此事件
}
});
事件日誌
- onTouch 返回false時
D/ViewEvent: ╔════════════════════════════════════════════════════════════════════════════════════════
D/ViewEvent: ║ onTouch (ViewActivity.java:39) [ALog-Content] 自定義View的onTouch事件,event:0 //觸摸事件中的按下(down)事件
D/ViewEvent: ╚════════════════════════════════════════════════════════════════════════════════════════
D/ViewEvent: ╔════════════════════════════════════════════════════════════════════════════════════════
D/ViewEvent: ║ onTouch (ViewActivity.java:39) [ALog-Content] 自定義View的onTouch事件,event:2 //觸摸事件中的滑動(move)事件
D/ViewEvent: ╚════════════════════════════════════════════════════════════════════════════════════════
D/ViewEvent: ╔════════════════════════════════════════════════════════════════════════════════════════
D/ViewEvent: ║ onTouch (ViewActivity.java:39) [ALog-Content] 自定義View的onTouch事件,event:1 //觸摸事件中的放開(up)事件
D/ViewEvent: ╚════════════════════════════════════════════════════════════════════════════════════════
D/ViewEvent: ╔════════════════════════════════════════════════════════════════════════════════════════
D/ViewEvent: ║ onClick (ViewActivity.java:33) [ALog-Content] 自定義View的onClick事件
D/ViewEvent: ╚════════════════════════════════════════════════════════════════════════════════════════
2.onTouch 返回true時
D/ViewEvent: ╔════════════════════════════════════════════════════════════════════════════════════════
D/ViewEvent: ║ onTouch (ViewActivity.java:39) [ALog-Content] 自定義View的onTouch事件,event:0
D/ViewEvent: ╚════════════════════════════════════════════════════════════════════════════════════════
D/ViewEvent: ╔════════════════════════════════════════════════════════════════════════════════════════
D/ViewEvent: ║ onTouch (ViewActivity.java:39) [ALog-Content] 自定義View的onTouch事件,event:2
D/ViewEvent: ╚════════════════════════════════════════════════════════════════════════════════════════
D/ViewEvent: ╔════════════════════════════════════════════════════════════════════════════════════════
D/ViewEvent: ║ onTouch (ViewActivity.java:39) [ALog-Content] 自定義View的onTouch事件,event:1
D/ViewEvent: ╚════════════════════════════════════════════════════════════════════════════════════════
總結:區別在於onTouch返回值,會影響到onClick是否會接收到事件
事件源碼分析
View中的dispatchTouchEvent()如何被調用
前提:熟知當視圖UI發生點擊事件後,最先響應的是Activity的dispatchTouchEvent()函數,那麼接下來的源碼就以dispatchTouchEvent()爲源頭開始分析
Activity中的dispatchTouchEvent():
/**
* Called to process touch screen events. You can override this to
* intercept all touch screen events before they are dispatched to the
* window. Be sure to call this implementation for touch screen events
* that should be handled normally.
* 譯文:被調用來處理觸屏事件。你可以重寫該方法來攔截所有觸屏事件被分發到窗口之前。對於應該正常處理的觸屏事件,需確保調用該實現
* @param ev The touch screen event.//觸屏事件
*
* @return boolean Return true if this event was consumed.//如果事件被消化則返回true
*/
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
- 當我們查看
getWindow()
可知返回對象是Window
,而Window
又是一個抽象類,它只有一個唯一的實現類是PhoneWindow.java
(從類說明驗證)
/**
* Abstract base class for a top-level window look and behavior policy. An
* instance of this class should be used as the top-level view added to the
* window manager. It provides standard UI policies such as a background, title
* area, default key processing, etc.
*
* <p>The only existing implementation of this abstract class is
* android.view.PhoneWindow, which you should instantiate when needing a
* Window.//該抽象類的唯一實現類是android.view.PhoneWindow,....
*/
public abstract class Window {
...
}
- 根據
getWindow().superDispatchTouchEvent(ev)
我們應該到PhoneWindow.java
類中查找superDispatchTouchEvent
方法,這裏,出現了DecorView
對象mDecor
,並且在這裏是調用了mDecor.superDispatchTouchEvent(event);
後直接返回
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
- 關鍵點來了,DecorView是個什麼類?類中的
superDispatchTouchEvent(event)
裏面執行了什麼東西?
首先,DecorView是Activity窗口的根視圖
,不是很明白的可以查找下相關資料,
其次,代碼如下
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
到這裏,dispatchTouchEvent
這個就不陌生了吧,就是在這裏調用到View中具體實現的dispatchTouchEvent(MotionEvent ev)
爲何onTouch返回true時onClick不被調用
從上面我們已經知道了dispatchTouchEvent
是作爲事件的源頭,那麼接下就以View.java
中的dispatchTouchEvent
作爲源頭進行起點分析
/**
* Pass the touch screen motion event down to the target view, or this
* view if it is the target.
*
* @param event The motion event to be dispatched.
* @return True if the event was handled by the view, false otherwise.
*/
public boolean dispatchTouchEvent(MotionEvent event) {
......
//分析步驟 3
//result 爲該方法的返回值,同時也是控制是否調用onClick事件的標記位,
//當爲true表示消耗事件時,則將不會調用到onClick
boolean result = false;
......
if (onFilterTouchEventForSecurity(event)) {
....
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
//分析步驟 2
//在這裏,我們姑且把li.mOnTouchListener.onTouch(this, event)前面的&&運算結果理解爲true(其實條件也正常也是true),
//並且li.mOnTouchListener.onTouch(this, event)的返回值決定是否result = true;
result = true;
}
//分析步驟 1
//縱觀整個方法體內容,與事件有關的,猜測也只剩下這個if條件了,並且在這個&&運算的條件中,result起着至關重要的前提
if (!result && onTouchEvent(event)) {
result = true;
}
}
......
return result;
}
以上dispatchTouchEvent方法內,忽略掉了部分與本次內容無關的源碼,需要全部源碼的,可自行查閱26版本的SDK
- 首先需先分析出onClick是在哪裏被調用
- 其次分析調用的前提條件
- 再來纔是閱讀分析前提條件的產生
- 最後梳理整個目的流程
道路千萬條,辦法無數種,我找到了performClick()
這個方法,因爲裏面就有我想要的邏輯,並且從方法註釋中得到了驗證
- onClick是在哪裏被調用
譯文部分和註釋部分爲分析內容
/**
* Call this view's OnClickListener, if it is defined. Performs all normal
* actions associated with clicking: reporting accessibility event, playing
* a sound, etc.
*
* @return True there was an assigned OnClickListener that was called, false
* otherwise is returned.
* 譯文:如果該視圖的OnClickListener被定義了,則調用它。......
* 意思就是如果這個自定義View被View.setOnClickListener,那麼performClick()就會被調用(當然,前提是事件不被攔截)
*/
public boolean performClick() {
final boolean result;
final ListenerInfo li = mListenerInfo;
if (li != null && li.mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
//這裏回調到了使用者以匿名內部類的方式實例出來的接口實現類,
//這個回調是否被使用,就是直接體現了onClick是否被調用,它就是我們今天的目標
li.mOnClickListener.onClick(this);
result = true;
} else {
result = false;
}
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
notifyEnterOrExitForAutoFillIfNeeded(true);
return result;
}
- onClick是否被調用的條件
根據上面的源碼進行分析,onClick
是否被調用的條件並不在這裏,那麼就往上分析,是誰調用了performClick()
?
如果進行搜索的話,我們會發現performClick()
的調用者有不少,但是,以dispatchTouchEvent
作爲前提,誰與onTouchEvent
有關,誰就是我們的目標,所以我們就在onTouchEvent
中找到了performClick()
的調用
/**
* Implement this method to handle touch screen motion events.
* <p>
* If this method is used to detect click actions, it is recommended that
* the actions be performed by implementing and calling
* {@link #performClick()}. This will ensure consistent system behavior,
* including:
* <ul>
* <li>obeying click sound preferences
* <li>dispatching OnClickListener calls
* <li>handling {@link AccessibilityNodeInfo#ACTION_CLICK ACTION_CLICK} when
* accessibility features are enabled
* </ul>
*
* @param event The motion event.
* @return True if the event was handled, false otherwise.
*/
public boolean onTouchEvent(MotionEvent event) {
......
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
case MotionEvent.ACTION_UP:
......
if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
......
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();
}
}
}
......
}
......
break;
case MotionEvent.ACTION_DOWN:
....
break;
case MotionEvent.ACTION_CANCEL:
....
break;
case MotionEvent.ACTION_MOVE:
....
break;
}
return true;
}
return false;
}
到這裏,已經實力驗證了onTouchEvent
是否執行的條件就是決定了onClick
是否被調用的條件
- onTouchEvent事件執行的條件
onTouchEvent
是在dispatchTouchEvent
中被調用,所以迴歸到dispatchTouchEvent
函數內的註釋說明進行閱讀理解 - 梳理總結
在整個事件的分發中,從Activity
視圖層接收到觸屏事件開始,onTouch
的返回值決定了result
的值,即決定了是否執行onClick
,所以onTouch
返回值爲false
的時候,onTouchEvent
纔有機會被執行,onClick
纔有機會被調用