1.View點(diǎn)擊事件的傳遞規(guī)則
首先,用戶觸摸屏幕的時(shí)候系統(tǒng)必須對事件做出相應(yīng)反應(yīng).而這個(gè)事件就是產(chǎn)生一個(gè)MotionEvent然后按照一定的規(guī)則傳遞給每一個(gè)View去進(jìn)行相應(yīng)的處理.這就是我們所謂的事件分發(fā)了.點(diǎn)擊事件的分發(fā)主要設(shè)計(jì)一下幾個(gè)主要的方法:
用來進(jìn)行事件的分發(fā).如果有事件傳遞給當(dāng)前的View,那么該View一定會(huì)去調(diào)用這個(gè)方法
返回值受當(dāng)前View的onTouchEvent和下級View的dispatchTouchEvent的影響
返回值表示當(dāng)前的事件時(shí)候已經(jīng)被處理完成
public boolean dispatchTouchEvent(MotionEvent e)
這個(gè)方法在上面那個(gè)方法中內(nèi)部調(diào)用,用來判斷是否攔截這個(gè)事件
如果當(dāng)前View攔截了某個(gè)事件 那么在同一個(gè)事件序列中就不會(huì)再次被調(diào)用
返回值表示是否攔截當(dāng)前事件
public boolean onInterceptTouchEvent(MotionEvent e)
也是在第一個(gè)方法中去調(diào)用 用來處理攔截下來的事件
返回值為真表示該事件已經(jīng)被處理 否則 沒有處理
在同一事件序列中View無法再次接收到事件
public boolean onTouchEvent(MotionEvent e)
可以用一段偽代碼來表示一下三者的關(guān)系:
public boolean dispatchTouchEvent(MotionEvent e) {
boolean consume = false;
if (onInterceptTouchEvent(e)) {
consume = onTouchEvent(e);
} else {
cnsume = childView.dispathcTouchEvent(e);
}
return cosume;
}
從上面的代碼中我們基本可以總結(jié)出這樣的結(jié)論:
對于一個(gè)根ViewGroup來說,當(dāng)接收到一個(gè)MotionEvent的時(shí)候:
調(diào)用dispatchTouchEvent方法調(diào)用onInterceptTouchEvent方法返回值為true,則表示攔截當(dāng)前事件,調(diào)用onTouchEvent來處理這個(gè)事件
返回值為false,則當(dāng)前事件將會(huì)被傳遞給childView,childView繼續(xù)調(diào)用dispatchTouchEvent方法
如此往復(fù),直至事件被處理.
當(dāng)一個(gè)View需要處理一個(gè)事件的時(shí)候
如果該View設(shè)置了onTouchListener,則會(huì)調(diào)用onTouch方法如果onTouch方法返回false,則去調(diào)用onTouchEvent如果設(shè)置了onClickListener,那么在onTouchEvent方法中將會(huì)調(diào)用onClick方法
反之,onTouchEvent則不會(huì)被調(diào)用
當(dāng)一個(gè)事件產(chǎn)生的時(shí)候,它的傳遞過程遵循這樣的過程:Activity->Windows->View;事件總是先傳遞給Activity,Activity在傳遞給Windows,Windows在傳遞給View;如果View將事件處理了,則該事件相應(yīng)就結(jié)束了.否則,事件將一級一級的繼續(xù)返回,最終會(huì)傳遞給Activity的onTouchEvent處理.
@Override
public boolean onTouchEvent(MotionEvent event) {
Toast.makeText(MainActivity.this, event.getAction() + "我是Activity", Toast.LENGTH_SHORT).show();
return super.onTouchEvent(event);
}
當(dāng)你沒有給任何控件設(shè)置相應(yīng)事件的時(shí)候(也就是都會(huì)返回false),那么你就會(huì)看到Activity的onTouchEvent被調(diào)用了.
在開發(fā)藝術(shù)這本書中提到了幾個(gè)結(jié)論:
1.同一個(gè)事件序列是指手指接觸屏幕那一刻起,到手指離開屏幕那一刻結(jié)束,在這個(gè)過程中所有產(chǎn)生的事件都屬于這一個(gè)事件序列.包括一個(gè)ACTION_DOWN,一個(gè)ACTION_UP和n個(gè)ACTION_MOVE;
2.某一個(gè)View一旦決定攔截事件,那么這一事件序列都只能由它來處理.
這個(gè)結(jié)論認(rèn)真想了一下,似乎有點(diǎn)問題;假如這個(gè)View我設(shè)置了onTouchListener,但是我依然返回false,這個(gè)事件序列仍然會(huì)傳遞給父View,當(dāng)然了,這個(gè)View也只能接收到一個(gè)ACTION_DOWN事件,ACTION_UP和ACTION_MOVE不會(huì)接收到.假如這個(gè)View同時(shí)還設(shè)置了onClickListener,onTouchEvent返回false的時(shí)候事件會(huì)交給onTouchEvent處理這個(gè)事件,不會(huì)在交給父View處理了.這個(gè)問題還是需要結(jié)合源碼來看一下;
3.某個(gè)View一旦開始處理一個(gè)事件,如果它不消耗ACTION_DOWN事件,那么同一時(shí)間序列也不會(huì)交給他來處理了.
4.如果View不消耗ACTION_DWON以外的事件,那么這個(gè)點(diǎn)擊事件就會(huì)消失,不會(huì)在交還給父View處理.最后會(huì)交回activity處理.
5.ViewGroup默認(rèn)不攔截任何事件,他的onInterceptTouch方法默認(rèn)返回false;View沒有onInterceptTouch方法,收到事件他的onTouchEvent事件就會(huì)被調(diào)用.
6.View的onTouchEvent默認(rèn)都會(huì)消耗事件(返回true),除非他是不可點(diǎn)擊的(clickable和龍Clickable同時(shí)為false).View的longClickable默認(rèn)都是false,clickable分情況.
2.Activity對事件的分發(fā)
點(diǎn)擊事件用MotionEvent來表示,當(dāng)點(diǎn)擊事件發(fā)生的時(shí)候,事件最先傳遞給當(dāng)前Activity,由Activity的dispatchTouchEvent來進(jìn)行事件派發(fā),具體工作是由Activity內(nèi)部的Windows來處理的.Windows會(huì)將事件傳遞給decor view,即當(dāng)前View的root view.先看一下Activity的dispatchTouchEvent源碼:
public boolean diapatchTouchEvent(MotionEvent e) {
if (e.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindows.superDispatchTouchEvent()) {
return true;
}
return onTouchEvent(e);
}
這里有個(gè)onUserInteraction
方法,點(diǎn)進(jìn)去發(fā)現(xiàn)這個(gè)方法是一個(gè)空方法,文檔是這樣寫的:
Called whenever a key, touch, or trackball event is dispatched to the activity. Implement this method if you wish to know that the user has interacted with the device in some way while your activity is running. This callback and onUserLeaveHint() are intended to help activities manage status bar notifications intelligently; specifically, for helping activities determine the proper time to cancel a notfication. All calls to your activity’s onUserLeaveHint() callback will be accompanied by calls to onUserInteraction(). This ensures that your activity will be told of relevant user activity such as pulling down the notification pane and touching an item there. Note that this callback will be invoked for the touch down action that begins a touch gesture, but may not be invoked for the touch-moved and touch-up actions that follow.
大體意思就是說onUserInteraction是幫助我們知道用戶開始和屏幕進(jìn)行交互的回調(diào)函數(shù).另外,還會(huì)和onUserLeaveHint
一起更加智能的管理狀態(tài)欄通知.
Called as part of the activity lifecycle when an activity is about to go into the background as the result of user choice. For example, when the user presses the Home key, onUserLeaveHint() will be called, but when an incoming phone call causes the in-call Activity to be automatically brought to the foreground, onUserLeaveHint() will not be called on the activity being interrupted. In cases when it is invoked, this method is called right before the activity’s onPause() callback. This callback and onUserInteraction() are intended to help activities manage status bar notifications intelligently; specifically, for helping activities determine the proper time to cancel a notfication.
這里這兩個(gè)方法對我們不是很重要了,根據(jù)分析可以知道,activity通過windows來分發(fā)事件,當(dāng)所有的view都沒有接收處理事件的時(shí)候,activity就會(huì)自己調(diào)用自己的onTouchEvent()來處理這個(gè)事件了.
繼續(xù)看getWindows.superDispatchTouchEvent()
,window
是個(gè)抽象類,
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. The only existing implementation of this abstract class is android.view.PhoneWindow, which you should instantiate when needing a Window.
根據(jù)文檔的描述,我們可以知道window
的唯一實(shí)現(xiàn)類是android.view.PhoneWindow
,那么他的dispatchTouchEvent
是怎么實(shí)現(xiàn)的呢?
//PhoneWindow#superDispatchTouchEvent
public boolean superDispatchTouchEvent(MotionEvent e) {
return mDecor.superDispatchTouchEvent(e);
}
這里的思路很清晰了,直接分發(fā)給mDecor處理,那么DecorView是什么東西呢?關(guān)于activity的層次點(diǎn)擊這里
//this is the top-level view of the window,containing the window decor(裝飾)
private final class DecorView extends FrameLayout implements RootViewSurfaceTaker
private DecorView mDecor;
@Override
public final View getDecorView() {
if (null == mDecor) {
installDecor();
}
return mDecor;
}
到了這事件會(huì)繼續(xù)分發(fā),到我們通過setContentView設(shè)置的ViewGroup那里繼續(xù)處理.
3.ViewGroup對事件的分發(fā)
// Check for interception.
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN|| mFirstTouchTarget != null) {
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 {
// There are no touch targets and this action is not an initial down
// so this view group continues to intercept touches.
intercepted = true;
}
這段代碼是來判斷是否要來攔截當(dāng)前的點(diǎn)擊事件的.可以看出當(dāng)這個(gè)事件是一個(gè)事件序列的開端,也就是一個(gè)ACTION_DOWN,就用去調(diào)用onInterceptTouch方法去判斷是否要去攔截這個(gè)事件;或者當(dāng)mFirstTouchTarget不為空的時(shí)候,也會(huì)去判斷.相反,就不會(huì)攔截了.這也說明了一個(gè)事件如果View不去處理他的ACTION_DWON事件為什么就能在去處理其他的事件了.
當(dāng)ViewGroup不處理事件要繼續(xù)分發(fā)的時(shí)候代碼是這樣的:
final int childrenCount = mChildrenCount;
if (newTouchTarget == null && childrenCount != 0) {
final float x = ev.getX(actionIndex);
final float y = ev.getY(actionIndex);
// Find a child that can receive the event.
// Scan children from front to back.
final ArrayList<View> preorderedList = buildOrderedChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = customOrder
? getChildDrawingOrder(childrenCount, i) : i;
final View child = (preorderedList == null)
? children[childIndex] : preorderedList.get(childIndex);
// If there is a view that has accessibility focus we want it
// to get the event first and if not handled we will perform a
// normal dispatch. We may do a double iteration but this is
// safer given the timeframe.
if (childWithAccessibilityFocus != null) {
if (childWithAccessibilityFocus != child) {
continue;
}
childWithAccessibilityFocus = null;
i = childrenCount - 1;
}
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
newTouchTarget = getTouchTarget(child);
if (newTouchTarget != null) {
// Child is already receiving touch within its bounds.
// Give it the new pointer in addition to the ones it is handling.
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}
resetCancelNextUpFlag(child);
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// Child wants to receive touch within its bounds.
mLastTouchDownTime = ev.getDownTime();
if (preorderedList != null) {
// childIndex points into presorted list, find original index
for (int j = 0; j < childrenCount; j++) {
if (children[childIndex] == mChildren[j]) {
mLastTouchDownIndex = j;
break;
}
}
} else {
mLastTouchDownIndex = childIndex;
}
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
// The accessibility focus didn't handle the event, so clear
// the flag and do a normal dispatch to all children.
ev.setTargetAccessibilityFocus(false);
}
if (preorderedList != null) preorderedList.clear();
}
邏輯也比清晰,遍歷ViewGroup的所有子View,找出能接受事件的所有元素;要滿足兩個(gè)條件:1.坐標(biāo)是否落在子View中2.是否正在播放動(dòng)畫.滿足這兩個(gè)條件,就會(huì)分發(fā)給他來處理,要是返回了true就表示事件已經(jīng)被處理,mFirstTouchTarget就會(huì)被賦值并終止此次分發(fā),否則繼續(xù)分發(fā)過程.
4.View對點(diǎn)擊事件的處理過程
//View#dispatchTouchEvent
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;
}
}
這里相對比較簡單,首先判斷是否設(shè)置了onTouchListener,如果設(shè)置了就去調(diào)用onTouch方法,如果返回了false,則去調(diào)用onTouchEvent方法; 在view設(shè)置了onClickListener或者onLongClickListener后,會(huì)自動(dòng)將CLICKABLE或者LONG_CLICKABLE變成ture;
最后三張圖十分清晰
效果圖如下:
View不處理事件流程圖(View沒有消費(fèi)事件)
View處理事件

事件攔截

附錄
以后每個(gè)知識點(diǎn)的實(shí)踐學(xué)習(xí)代碼會(huì)上傳到我的GitHub,歡迎大家一起學(xué)習(xí)-.-~