CVTE一面答的很很差的題目之一,特別寫一篇博客反思本身。我記得我當時答的糊里糊塗的,只說了連事件從何時開始響應的也沒說,只說了從子控件開始接受到,若是不消費,或者沒有子控件可以消費時,就向上傳遞,一直傳到根佈局後自動消費。java
所謂的安卓事件是什麼?具體來講的就是點擊和滑動兩個操做;抽象着來講就是下面的表格。函數
MotionEvent/事件類型 | 具體操做 |
---|---|
ACTION_DOWN | 點下View |
ACTION_UP | 擡起View |
ACTION_MOVE | 滑動View |
ACTION_CANCEL | 非人爲因素取消 |
事件序列通常組成:佈局
點擊的事件組成就是:Down --> Uppost
滑動的事件組成就是:Down --> Move --> Move .... --> Upthis
public boolean dispatchTouchEvent(MotionEvent ev) {
// 從判斷語句中能夠得出全部事件的起點就是Down
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
// 實現屏保功能
onUserInteraction();
}
// 向上傳遞至ViewGroup,調用其dispatchTouchEvent
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
複製代碼
文章中我已經添加了註釋內容,其中getWindow()
得到就是一個Window
抽象類,根據其子類PhoneWindow
咱們能夠很容易得知最後調用的其實就是ViewGroup
的dispatchTouchEvent()
方法spa
/** * 實際上就是判斷事件是不是DOWN事件,event的座標是否在邊界內等 */
public boolean onTouchEvent(MotionEvent event) {
if (mWindow.shouldCloseOnTouch(this, event)) {
finish();
return true;
}
return false;
}
複製代碼
最後就是Activity
中的onTouchEvent()
方法了,這個模塊乾的事情在註釋中也就很清晰明瞭了。rest
public boolean dispatchTouchEvent(MotionEvent ev) {
········
// 初始化Down事件
if (actionMasked == MotionEvent.ACTION_DOWN) {
// 丟棄以前手頭上乾的事情,從新開始響應Down事件
cancelAndClearTouchTargets(ev);
resetTouchState();
}
// 檢查是否須要攔截
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
// 這個與運算是用於影響除Down之外的事件的
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
} else {
// 當前按壓的位置沒有控件,或者當前控件並不可被點擊,直接被ViewGroup攔截
intercepted = true;
}
········
/** *這個判斷裏面一樣的仍是判斷響應的事件,而後就是經過一個for循環判斷位置來判斷當前的子控件是否在對應的位置內 * 還有很是重要的一點就是這個循環的判斷仍是倒敘的 */
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked ==MotionEvent.ACTION_HOVER_MOVE) {
········
if (newTouchTarget == null && childrenCount != 0) {
········
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);
// 後面的篇幅主要用於判斷當前的控件的各類屬性是不是知足須要的。好比說位置、是否能夠點擊、是否隱藏等一系列信息
········
}
········
}
複製代碼
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (ev.isFromSource(InputDevice.SOURCE_MOUSE)
&& ev.getAction() == MotionEvent.ACTION_DOWN
&& ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY)
&& isOnScrollbarThumb(ev.getX(), ev.getY())) {
return true;
}
return false;
}
複製代碼
由onInterceptTouchEvent(MotionEvent ev)
函數可知,默認其實並不會去攔截。因此就通常狀況而言,dispatchTouchEvent()
方法是須要去循環遍歷子控件集合去尋找對應的控件的。code
使用一個僞代碼解釋以上的邏輯orm
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean consume = false;
if(onInterceptTouchEvent(ev)){
// 本身攔截,本身消費
onTouchEvent(ev);
}else{
// 不攔截,分發給子View進行消費
consume = child.dispatchTouchEvent(ev);
}
return consume;
}
複製代碼
public boolean dispatchTouchEvent(MotionEvent event) {
·····
boolean result = false;
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(event, 0);
}
if (onFilterTouchEventForSecurity(event)) {
·····
//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;
}
}
if (!result && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
}
return result;
}
複製代碼
經過以往的實踐,咱們知道只有經過設置了監聽器的View纔可以去監聽事件,那麼在dispatchTouchEvent()
方法中也是同樣的,若是View並無被設置監聽器,變量result
也不會被賦值成爲true。cdn
從代碼中很容易看出
onTouch()
方法的優先級大於onTouchEvent()
方法。
public boolean onTouchEvent(MotionEvent event) {
final float x = event.getX();
final float y = event.getY();
final int viewFlags = mViewFlags;
final int action = event.getAction();
final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
·····
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
case MotionEvent.ACTION_UP:
·····
boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
boolean focusTaken = false;
if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
focusTaken = requestFocus();
}
if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
removeLongPressCallback();
if (!focusTaken) {
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
performClickInternal();
}
}
}
·····
}
·····
mIgnoreNextUpEvent = false;
break;
·····
}
return true;
}
return false;
}
複製代碼
在onTouchEvent()
方法中其實具體幹了一件事情,那就是區別究竟是長按事件仍是點擊事件。
那麼先行判斷的是長按事件仍是點擊事件呢?答案很明顯,在代碼行中removeLongPressCallback();
有一個這樣的函數,這就是去除長按事件回調的函數,因此答案就是長按事件是第一個被判斷的事件,而後纔是點擊事件。
判斷這個方法的事件的方法就是經過作出Up動做時的時間和作出Down動做時的時間間隔。若是Down和Up兩個動做之間的時間間隔小於500ms,就是點擊事件。