1、什麼是事件傳遞?
Android事件傳遞是指用戶操作屏幕產生的一系列動作事件(按下、滑動、擡起)從外層傳遞到的內層的過程。
2、外層到內層如何理解?
Activity —> Window ----> ViewGroup ---->View
3、必須瞭解的基礎
3.1 首先要知道傳遞的對象是一個MotionEvent類的對象。這個類中定義了動作常量,比如“按下” public static final int ACTION_DOWN = 0等等。
3.2 第二要知道3個重要的方法 :
boolean dispatchTouchEvent(MotionEvent ev) 分發事件ev
boolean onInterceptTouchEvent(MotionEvent ev) 是否攔截事件ev
boolean onTouchEvent(MotionEvent ev) 是否消耗事件ev
3.3經常給view設置的有關事件監聽的方法
view.setOnTouchListener(…) 觸摸事件監聽
view.setOnClickListener(…) 單擊事件監聽
4、事件傳遞的具體過程
4.1產生事件:
手指觸碰屏幕首先產生一個MotionEvent Down事件,隨着手指滑動產生多個MotionEvent Move事件,最後擡起手指產生一個MotionEvent Up事件
4.2 事件在Activity中:
Activity中有dispatchTouchEvent()、onTouchEvent()2個方法。
/**
* 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.
*/
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
/**
* Called when a touch screen event was not handled by any of the views
* under it. This is most useful to process touch events that happen
* outside of your window bounds, where there is no view to receive it.
*
* @param event The touch screen event being processed.
*
* @return Return true if you have consumed the event, false if you haven't.
* The default implementation always returns false.
*/
public boolean onTouchEvent(MotionEvent event) {
if (mWindow.shouldCloseOnTouch(this, event)) {
finish();
return true;
}
return false;
}
dispatchTouchEvent中事件先去傳給getWindow().superDispatchTouchEvent(ev)去做判斷,如果返回爲true,則直接結束。如果爲false 觸發Activity的onTouchEvent()方法。
也就是說在Activity中通過getWindow().superDispatchTouchEvent(ev)方法將事件傳遞給了Window。
4.3 事件在Window中:
getWindow().superDispatchTouchEvent(ev)調用Window內部的superDispatchTouchEvent()方法。
/**
* Used by custom windows, such as Dialog, to pass the touch screen event
* further down the view hierarchy. Application developers should
* not need to implement or call this.
*
*/
public abstract boolean superDispatchTouchEvent(MotionEvent event);
這是一個抽象方法。具體邏輯在Window的唯一繼承類PhoneWidow中:
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
事件在window中沒有做任務處理,直接通過mDecor.superDispatchTouchEvent(event)傳遞給了DecorView。DecorView是view的最頂層,被定義在PhoneWindow中的一個對象。它是一個繼承自FrameLayout的ViewGroup。
// This is the top-level view of the window, containing the window decor.
private DecorView mDecor;
4.4 事件在ViewGroup中:
事件傳遞到DecorView的superDispatchTouchEvent()方法
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
調用了super.dispatchTouchEvent(event),這個super其實就是ViewGroup。
在ViewGroup中有dispatchTouchEvent()、onInterceptTouchEvent()、onTouchEvent()3個方法。
由於源碼過長,其中還包含很多控制單元,爲了分析過程直接貼出來不直觀,不想篇幅過長,自己去查看印象更深刻!直接總結:
(1)事件MotionEvent傳遞給dispatchTouchEvent(ev)進行事件分發
(2)在分發方法內部調用了onInterceptTouchEvent(ev)判讀是否對事件攔截(返回false代表不攔截,true表示攔截。默認爲false)。
(3) onInterceptTouchEvent(ev)返回true, 說明要攔截事件,ev會交給此viewGroup處理。
(4)如果viewGroup設置了OnTouchListener,onTouch()會被調用,onTouch()返回false viewGroup的onTouchEvent會被調用。onTouch()返回true viewGroup的onTouchEvent會被屏蔽掉。
(5)onInterceptTouchEvent(ev)返回false,不攔截事件,將會把事件傳遞給 子View(view或viewGroup)的dispatchTouchEvent(ev)處理。如果子view是ViewGroup則繼續從(1)開始循環。
4.4 事件在View中:
在View中有dispatchTouchEvent()、onTouchEvent()2個方法。沒有onInterceptTouchEvent()
與ViewGroup過程相似,畢竟ViewGroup繼承自View。ViewGroup也沒有重寫onTouchEvent,所以ViewGroup中onTouchEvent()方法就是View中的方法。
(1)事件MotionEvent傳遞給dispatchTouchEvent(ev)進行事件分發
(2)如果view設置了OnTouchListener,onTouch()會被調用,onTouch()返回false view的onTouchEvent會被調用。onTouch()返回true view的onTouchEvent會被屏蔽掉。
5、setOnTouchListener()\setOnClickListener()\onTouchEvent()調用關係:
下面是view的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) {
// If the event should be handled by accessibility focus first.
if (event.isTargetAccessibilityFocus()) {
// We don't have focus or no virtual descendant has it, do not handle the event.
if (!isAccessibilityFocusedViewOrHost()) {
return false;
}
// We have focus and got the event, then use normal event dispatch.
event.setTargetAccessibilityFocus(false);
}
boolean result = false;
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(event, 0);
}
final int actionMasked = event.getActionMasked();
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Defensive cleanup for new gesture
stopNestedScroll();
}
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;
}
}
if (!result && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
}
// Clean up after nested scrolls if this is the end of a gesture;
// also cancel it if we tried an ACTION_DOWN but we didn't want the rest
// of the gesture.
if (actionMasked == MotionEvent.ACTION_UP ||
actionMasked == MotionEvent.ACTION_CANCEL ||
(actionMasked == MotionEvent.ACTION_DOWN && !result)) {
stopNestedScroll();
}
return result;
}
其中判斷了mOnTouchListener != null && mOnTouchListener.onTouch(this, event)條件,之後根據返回結果判斷是否去調用onTouchEvent(event)。驗證了OnTouchListener 調用優先,並控制onTouchEvent()的調用。
在view的onTouchEvent()方法中
public boolean onTouchEvent(MotionEvent event) {
...代碼省略...
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
case MotionEvent.ACTION_UP:
...代碼省略...
if (!focusTaken) {
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
performClickInternal();
}
}
...代碼省略...
}
...代碼省略...
performClickInternal()方法會調performClick(),貼出performClick()源碼
public boolean performClick() {
// We still need to call this method to handle the cases where performClick() was called
// externally, instead of through performClickInternal()
notifyAutofillManagerOnClick();
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);
notifyEnterOrExitForAutoFillIfNeeded(true);
return result;
}
這裏觸發了li.mOnClickListener.onClick(this)。說明點擊事件onClick是在onTouchEvent中間接調用的。
6、事件交由上層處理
子視圖不能處理事件時候,onTouchEvent 返回false,事件將交給父視圖的onTouchEvent調用。如果還不能處理事件則依次往上傳遞。直到Activity。也就是在getWindow().superDispatchTouchEvent(ev)返回了false。將調用Activity的onTouchEvent()方法。