一、基礎知識
1.分發對象
事件:Touch事件相關細節(發生觸摸的位置、時間、歷史記錄、手勢動作等)被封裝成MotionEvent對象
2.事件
主要發生的Touch事件大致分爲以下四種:
-
MotionEvent.ACTION_DOWN:按下事件(所有事件的開始)
-
MotionEvent.ACTION_MOVE:滑動事件
-
MotionEvent.ACTION_CANCEL:非人爲原因結束本次事件
-
MotionEvent.ACTION_UP:擡起事件(與DOWN對應)
3.大體流程
4.主要方法
5.傳遞對象
一個點擊事件產生後,傳遞順序是:Activity(Window) -> ViewGroup -> View
二、流程介紹
文字敘述太繁瑣,圖更好理解
主要方法
主體流程
三、源碼分析
1.Activity的事件分發
當一個點擊事件發生時,事件最先傳到Activity的dispatchTouchEvent()進行事件分發
public boolean dispatchTouchEvent(MotionEvent ev) {
//一般事件列開始都是DOWN,所以這裏基本是true
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
//關注點1
onUserInteraction();
}
//關注點2
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
//關注點3
return onTouchEvent(ev);
}
關注點1
onUserInteraction:每當Key,Touch,Trackball事件分發到當前Activity就會被調用。如果你想當你的Activity在運行的時候,能夠得知用戶正在與你的設備交互,你可以override該方法。
關注點2
getWindow()可以得到一個Window對象,Window類是抽象類,且PhoneWindow是Window類的唯一實現類
superDispatchTouchEvent(ev)是抽象方法,通過PhoneWindow類中看一下superDispatchTouchEvent()的作用
@Overridepublic boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
//mDecor是DecorView的實例
//DecorView是視圖的頂層view,繼承自FrameLayout,是所有界面的父類
}
接下來看mDecor.superDispatchTouchEvent(event):
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
//DecorView繼承自FrameLayout
//那麼它的父類就是ViewGroup,而super.dispatchTouchEvent(event)方法,其實就應該是ViewGroup的dispatchTouchEvent()
}
從這開始ViewGroup的事件分發了
關注點3
當viewGroup分發事件失敗,Activity將會自己處理
2.ViewGroup的事件分發
ViewGroup的dispatchTouchEvent()源碼分析,該方法比較複雜,截取幾個重要的邏輯片段進行介紹,來解析整個分發流程。
// 發生ACTION_DOWN事件或者已經發生過ACTION_DOWN,並且將mFirstTouchTarget賦值,才進入此區域,主要功能是攔截器
final boolean intercepted;
if(actionMasked==MotionEvent.ACTION_DOWN||mFirstTouchTarget!=null){
//disallowIntercept:是否禁用事件攔截的功能(默認是false),即不禁用
//可以在子View通過調用requestDisallowInterceptTouchEvent方法對這個值進行修改,不讓該View攔截事件
final boolean disallowIntercept=(mGroupFlags&FLAG_DISALLOW_INTERCEPT)!=0;
//默認情況下會進入該方法
if(!disallowIntercept){
//調用攔截方法
intercepted=onInterceptTouchEvent(ev);
ev.setAction(action);
}else{
intercepted=false;
}
}else{
// 當沒有觸摸targets,且不是down事件時,開始持續攔截觸摸。
intercepted=true;
}
這一段的內容主要是爲判斷是否攔截。如果當前事件的MotionEvent.ACTION_DOWN,則進入判斷,調用ViewGroup onInterceptTouchEvent()方法的值,判斷是否攔截。如果mFirstTouchTarget != null,即已經發生過MotionEvent.ACTION_DOWN,並且該事件已經有ViewGroup的子View進行處理了,那麼也進入判斷,調用ViewGroup onInterceptTouchEvent()方法的值,判斷是否攔截。如果不是以上兩種情況,即已經是MOVE或UP事件了,並且之前的事件沒有對象進行處理,則設置成true,開始攔截接下來的所有事件。這也就解釋瞭如果子View的onTouchEvent()方法返回false,那麼接下來的一些列事件都不會交給他處理。如果VieGroup的onInterceptTouchEvent()第一次執行爲true,則mFirstTouchTarget = null,則也會使得接下來不會調用onInterceptTouchEvent(),直接將攔截設置爲true。
當ViewGroup不攔截事件的時候,事件會向下分發交由它的子View或ViewGroup進行處理。
/* 從最底層的父視圖開始遍歷, ** 找尋newTouchTarget,即上面的mFirstTouchTarget ** 如果已經存在找尋newTouchTarget,說明正在接收觸摸事件,則跳出循環。 */
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 (childWithAccessibilityFocus != null) {
if (childWithAccessibilityFocus != child) {
continue;
}
childWithAccessibilityFocus = null;
i = childrenCount - 1;
}
//如果view不可見,或者觸摸的座標點不在view的範圍內,則跳過本次循環
if (!canViewReceivePointerEvents(child) || !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
newTouchTarget = getTouchTarget(child);
// 已經開始接收觸摸事件,並退出整個循環。
if (newTouchTarget != null) {
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}
//如果觸摸位置在child的區域內,則把事件分發給子View或ViewGroup
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// 獲取TouchDown的時間點
mLastTouchDownTime = ev.getDownTime();
// 獲取TouchDown的Index
if (preorderedList != null) {
for (int j = 0; j < childrenCount; j++) {
if (children[childIndex] == mChildren[j]) {
mLastTouchDownIndex = j;
break;
}
}
} else {
mLastTouchDownIndex = childIndex;
}
//獲取TouchDown的x,y座標
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
//添加TouchTarget,則mFirstTouchTarget != null
newTouchTarget = addTouchTarget(child, idBitsToAssign);
//表示以及分發給NewTouchTarget
alreadyDispatchedToNewTouchTarget = true;
break;
}
dispatchTransformedTouchEvent()方法實際就是調用子元素的dispatchTouchEvent()方法。 其中dispatchTransformedTouchEvent()方法的重要邏輯如下:
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
handled = child.dispatchTouchEvent(event);
}
由於其中傳遞的child不爲空,所以就會調用子元素的dispatchTouchEvent()。 如果子元素的dispatchTouchEvent()方法返回true,那麼mFirstTouchTarget就會被賦值,同時跳出for循環。
//添加TouchTarget,則mFirstTouchTarget != null。
newTouchTarget = addTouchTarget(child, idBitsToAssign);
//表示以及分發給NewTouchTarget
alreadyDispatchedToNewTouchTarget = true;
其中在addTouchTarget(child, idBitsToAssign);內部完成mFirstTouchTarget被賦值。 如果mFirstTouchTarget爲空,將會讓ViewGroup默認攔截所有操作。 如果遍歷所有子View或ViewGroup,都沒有消費事件。ViewGroup會自己處理事件。
3.View的事件分發
public boolean dispatchTouchEvent(MotionEvent event) {
if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && mOnTouchListener.onTouch(this, event)) {
return true;
}
return onTouchEvent(event);
}
第一個條件:mOnTouchListener!= null
//mOnTouchListener是在View類下setOnTouchListener方法裏賦值的
public void setOnTouchListener(OnTouchListener l) {
//即只要我們給控件註冊了Touch事件,mOnTouchListener就一定被賦值(不爲空)
mOnTouchListener = l;
}
第二個條件:(mViewFlags & ENABLED_MASK) == ENABLED
-
該條件是判斷當前點擊的控件是否enable
-
由於很多View默認是enable的,因此該條件恆定爲true
第三個條件:mOnTouchListener.onTouch(this, event)
-
如果在onTouch方法返回true,就會讓上述三個條件全部成立,從而整個方法直接返回true。
-
如果在onTouch方法裏返回false,就會去執行onTouchEvent(event)方法。
public boolean onTouchEvent(MotionEvent event) {
final int viewFlags = mViewFlags;
if ((viewFlags & ENABLED_MASK) == DISABLED) {
// A disabled view that is clickable still consumes the touch
// events, it just doesn't respond to them.
return (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));
}
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
//如果該控件是可以點擊的就會進入到下兩行的switch判斷中去;
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
//如果當前的事件是擡起手指,則會進入到MotionEvent.ACTION_UP這個case當中。
switch (event.getAction()) {
case MotionEvent.ACTION_UP:
boolean prepressed = (mPrivateFlags & PREPRESSED) != 0;
// 在經過重重判斷之後,會執行到performClick()方法。
if ((mPrivateFlags & PRESSED) != 0 || prepressed) {
boolean focusTaken = false;
if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
focusTaken = requestFocus();
}
if (!mHasPerformedLongPress) {
removeLongPressCallback();
if (!focusTaken) {
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
performClick();
}
}
}
if (mUnsetPressedState == null) {
mUnsetPressedState = new UnsetPressedState();
}
if (prepressed) {
mPrivateFlags |= PRESSED;
refreshDrawableState();
postDelayed(mUnsetPressedState,
ViewConfiguration.getPressedStateDuration());
} else if (!post(mUnsetPressedState)) {
mUnsetPressedState.run();
}
removeTapCallback();
}
break;
case MotionEvent.ACTION_DOWN:
if (mPendingCheckForTap == null) {
mPendingCheckForTap = new CheckForTap();
}
mPrivateFlags |= PREPRESSED;
mHasPerformedLongPress = false;
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
break;
case MotionEvent.ACTION_CANCEL:
mPrivateFlags &= ~PRESSED;
refreshDrawableState();
removeTapCallback();
break;
case MotionEvent.ACTION_MOVE:
final int x = (int) event.getX();
final int y = (int) event.getY();
int slop = mTouchSlop;
if ((x < 0 - slop) || (x >= getWidth() + slop) ||
(y < 0 - slop) || (y >= getHeight() + slop)) {
removeTapCallback();
if ((mPrivateFlags & PRESSED) != 0) {
removeLongPressCallback();
mPrivateFlags &= ~PRESSED;
refreshDrawableState();
}
}
break;
}
//如果該控件是可以點擊的,就一定會返回true
return true;
}
//如果該控件是不可以點擊的,就一定會返回false
return false;
}
performClick()的源碼分析
public boolean performClick() {
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
if (mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
mOnClickListener.onClick(this);
return true;
}
return false;
}
只要mOnClickListener不爲null,就會去調用onClick方法;
那麼,mOnClickListener又是在哪裏賦值的呢?
public void setOnClickListener(OnClickListener l) {
if (!isClickable()) {
setClickable(true);
}
mOnClickListener = l;
}
四、總結
android事件產生後的傳遞過程是從Activity--->Window--->View的,即隧道式傳遞,而View又分爲不包含子 View的View以及包含子View的ViewGroup,事件產生之後首先傳遞到Activity上面,而Activity接着會傳遞到 PhoneWindow上,PhoneWindow會傳遞給RootView,而RootView其實就是DecorView了,接下來便是從 DecorView到View上的分發過程了,具體就可以分成ViewGroup和View的分發兩種情況了;
對於ViewGroup而言,當事件分發到當前ViewGroup上面的時候,首先會調用他的dispatchTouchEvent方法,在 dispatchTouchEvent方法裏面會調用onInterceptTouchEvent來判斷是否要攔截當前事件,如果要攔截的話,就會調用 ViewGroup自己的onTouchEvent方法了,如果onInterceptTouchEvent返回false的話表示不攔截當前事件,那麼 事件將會繼續往當前ViewGroup的子View上面傳遞了,如果他的子View是ViewGroup的話,則重複ViewGroup事件分發過程,如 果子View就是View的話,則轉到下面的View分發過程;
對於View而言,事件傳遞過來首先當然也是執行他的dispatchTouchEvent方法了,如果我們爲當前View設置了 onTouchListener監聽器的話,首先就會執行他的回調方法onTouch了,這個方法的返回值將決定事件是否要繼續傳遞下去了,如果返回 false的話,表示事件沒有被消費,還會繼續傳遞下去,如果返回true的話,表示事件已經被消費了,不再需要向下傳遞了;如果返回false,那麼將 會執行當前View的onTouchEvent方法,如果我們爲當前View設置了onLongClickListener監聽器的話,則首先會執行他的 回調方法onLongClick,和onTouch方法類似,如果該方法返回true表示事件被消費,不會繼續向下傳遞,返回false的話,事件會繼續 向下傳遞,爲了分析,我們假定返回false,如果我們設置了onClickListener監聽器的話,則會執行他的回調方法onClick,該方法是 沒有返回值的,所以也是我們事件分發機制中最後執行的方法了;可以注意到的一點就是隻要你的當前View是clickable或者 longclickable的,View的onTouchEvent方法默認都會返回true,也就是說對於事件傳遞到View上來說,系統默認是由 View來消費事件的,但是ViewGroup就不是這樣了;
上面的事件分發過程只是正常情況下的,如果有這樣一種情況,比如事件傳遞到最裏層的View之後,調用該View的oonTouchEvent方法返回了 false,那麼這時候事件將通過冒泡式的方式向他的父View傳遞,調用它父View的onTouchEvent方法,如果正好他的父View的 onTouchEvent方法也返回false的話,這個時候事件最終將會傳遞到Activity的onTouchEvent方法了,也就是最終就只能由 Activity自己來處理了;
事件分發機制需要注意的幾點:
(1):如果說除Activity之外的View都沒有消費掉DOWN事件的話,那麼事件將不再會傳遞到Activity裏面的子View了,將直接由Activity自己調用自己的onTouchEvent方法來處理了;
(2):一旦一個ViewGroup決定攔截事件,那麼這個事件序列剩餘的部分將不再會由該ViewGroup的子View去處理了,即事件將在此 ViewGroup層停止向下傳遞,同時隨後的事件序列將不再會調用onInterceptTouchEvent方法了;
(3):如果一個View開始處理事件但是沒有消費掉DOWN事件,那麼這個事件序列隨後的事件將不再由該View來處理,通俗點講就是你自己沒能力就別瞎BB,要不以後的事件就都不給你了;
(4):View的onTouchEvent方法是否執行是和他的onTouchListener回調方法onTouch的返回值息息相關 的,onTouch返回true,onTouchEvent方法不執行;onTouch返回false,onTouchEvent方法執行,因爲 onTouchEvent裏面會執行onClick,所以造成了onClick是否執行和onTouch的返回值有了關係;