事件分發(fā)機制主要是指觸摸事件在Activity螟加、ViewGroup直焙、View之間傳遞并消費的機制景东,分發(fā)順序為 Activity > ViewGroup > View奔誓;
主要方法:
VIewGroup相關:onInterceptTouchEvent()、dispatchTouchEvent()厨喂、onTouchEvent()
View相關:dispatchTouchEvent()和措、onTouchEvent()
1. Activity事件分發(fā)機制
一般情況下用戶按下Activity時,Activity會執(zhí)行dispatchTouchEvent方法蜕煌,開始分發(fā)流程,我們看下dispatchTouchEvent()方法源碼:
//activity的事件分發(fā)方法
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
dispatchTouchEvent方法里就兩個處理:
(1)MotionEvent.ACTION_DOWN事件執(zhí)行時會調用onUserInteraction()方法贫母;我們看看這個方法做了啥盒刚;
public void onUserInteraction() {
}
這個方法實現(xiàn)是空的,不過我們可以從注釋和其他途徑可以了解到誓酒,該方法主要的作用是實現(xiàn)屏保功能,并且當此 Activity 在棧頂?shù)臅r候贮聂,觸屏點擊 Home、Back歼冰、Recent 鍵等都會觸發(fā)這個方法耻警。
(2)接下來是if()語句執(zhí)行了getWindow().superDispatchTouchEvent(ev)方法甸怕,能看出來他是獲取了Window并調用了其superDispatchTouchEvent方法腮恩,但你會發(fā)現(xiàn)Window是個抽象類,superDispatchTouchEvent是個抽象方法武契,看不出任何東西荡含,然后通過源碼追蹤你會發(fā)現(xiàn)其實getWindow獲取的是Window的子類PhoneWindow;
final void attach(){
...
mWindow = new PhoneWindow(this, window, activityConfigCallback);
...
}
所以我們來看看PhoneWindow的superDispatchTouch方法:
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
直接調用了DecorView的superDispatchTouchEvent方法全释,DecorView繼承自FrameLayout误债,是頂層View,所有界面的父類,而FrameLayout是ViewGroup的子類躺盛,所以最終調用的是ViewGroup的dispatchTouchEvent方法。
public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {
...
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
...
}
2. ViewGroup的事件分發(fā)機制
我們來看看ViewGroup的dispatchTouchEvent方法(Android5.0前的方法槽惫,5.0之后的方法比較復雜辩撑,但原理一樣):
public boolean dispatchTouchEvent(MotionEvent ev) {
... // 僅貼出關鍵代碼
// 重點分析1:ViewGroup每次事件分發(fā)時,都需調用onInterceptTouchEvent()詢問是否攔截事件
if (disallowIntercept || !onInterceptTouchEvent(ev)) {
// 判斷值1:disallowIntercept = 是否禁用事件攔截的功能(默認是false)各薇,可通過調用requestDisallowInterceptTouchEvent()修改
// 判斷值2: !onInterceptTouchEvent(ev) = 對onInterceptTouchEvent()返回值取反
// a. 若在onInterceptTouchEvent()中返回false(即不攔截事件)君躺,就會讓第二個值為true棕叫,從而進入到條件判斷的內部
// b. 若在onInterceptTouchEvent()中返回true(即攔截事件),就會讓第二個值為false俺泣,從而跳出了這個條件判斷
// c. 關于onInterceptTouchEvent() ->>分析1
ev.setAction(MotionEvent.ACTION_DOWN);
final int scrolledXInt = (int) scrolledXFloat;
final int scrolledYInt = (int) scrolledYFloat;
final View[] children = mChildren;
final int count = mChildrenCount;
// 重點分析2
// 通過for循環(huán),遍歷了當前ViewGroup下的所有子View
for (int i = count - 1; i >= 0; i--) {
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE
|| child.getAnimation() != null) {
child.getHitRect(frame);
// 判斷當前遍歷的View是不是正在點擊的View横漏,從而找到當前被點擊的View
// 若是,則進入條件判斷內部
if (frame.contains(scrolledXInt, scrolledYInt)) {
final float xc = scrolledXFloat - child.mLeft;
final float yc = scrolledYFloat - child.mTop;
ev.setLocation(xc, yc);
child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
// 條件判斷的內部調用了該View的dispatchTouchEvent()
// 即 實現(xiàn)了點擊事件從ViewGroup到子View的傳遞(具體請看下面的View事件分發(fā)機制)
if (child.dispatchTouchEvent(ev)) {
mMotionTarget = child;
return true;
// 調用子View的dispatchTouchEvent后是有返回值的
// 若該控件可點擊铝宵,那么點擊時华畏,dispatchTouchEvent的返回值必定是true,因此會導致條件判斷成立
// 于是給ViewGroup的dispatchTouchEvent()直接返回了true侣夷,即直接跳出
// 即把ViewGroup的點擊事件攔截掉
}
}
}
}
}
}
boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) ||
(action == MotionEvent.ACTION_CANCEL);
if (isUpOrCancel) {
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
}
final View target = mMotionTarget;
// 重點分析3
// 若點擊的是空白處(即無任何View接收事件) / 攔截事件(手動復寫onInterceptTouchEvent()百拓,從而讓其返回true)
if (target == null) {
ev.setLocation(xf, yf);
if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {
ev.setAction(MotionEvent.ACTION_CANCEL);
mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
}
return super.dispatchTouchEvent(ev);
// 調用ViewGroup父類的dispatchTouchEvent()晰甚,即View.dispatchTouchEvent()
// 因此會執(zhí)行ViewGroup的onTouch() ->> onTouchEvent() ->> performClick() ->> onClick(),即自己處理該事件蓖捶,事件不會往下傳遞(具體請參考View事件的分發(fā)機制中的View.dispatchTouchEvent())
// 此處需與上面區(qū)別:子View的dispatchTouchEvent()
}
...
}
(1)ViewGroup分發(fā)事件時首先判斷當前Viewgroup有沒有攔截事件onInterceptTouchEvent(ev)扁远,若返回值為true則表明已經攔截了此事件,跳出判斷并闲,事件不會往下傳遞谷羞,若是flase則相反,事件會往下傳遞购公。
(2)獲取到ViewGroup的所有子View及子ViewGroup雁歌,通過for循環(huán)遍歷;先判斷次子View是否是正在點擊的View(通過位置判斷)比庄,如果是的話調用此子View的dispatchTouchEvent方法,這個方法是有返回值的制恍,如果返回值為true就表明事件已被消費神凑,跳出整個循環(huán),不再往下傳遞鹃唯,事件到此結束瓣喊。
(3)如果點擊的是空白處或事件被攔截的情況下會調用父類的dispatchTouchEvent()方法,即View.dispatchTouchEvent()洪橘,自己處理該事件棵帽,不會再往下傳遞。
3. View事件的分發(fā)機制
View的分發(fā)機制也是從dispatchTouchEvent()開始的弟晚,我們分析下源碼指巡;
public boolean dispatchTouchEvent(MotionEvent event) {
if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
mOnTouchListener.onTouch(this, event)) {
return true;
}
return onTouchEvent(event);
}
方法里有一個if判斷隶垮,里面有三個條件秘噪,若滿足了這三個條件會返回true,即消費了該事件蹋偏,否則執(zhí)行onTouchEvent()至壤,我們逐一分析這三個條件;
(1)mOnTouchListener != null黎棠,需要再View.setOnTouchListener()里注冊Touch事件;
(2)(mViewFlags & ENABLED_MASK) == ENABLED木西,判斷當前View是否enable随静,很多View默認enable;
(3)mOnTouchListener.onTouch(this, event) 回調以注冊的Touch方法:
button.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
return false;
}
});
若onTouch返回了true恋捆,上述三個條件全部成立扛门,從而使得View.dispatchTouchEvent()直接返回true,事件分發(fā)結束
若onTouch返回了false星立,上述三個條件不成立葬凳,執(zhí)行onTouchEvent(event);
4. View.onTouchEvent()源碼分析
public boolean onTouchEvent(MotionEvent event) {
final int viewFlags = mViewFlags;
if ((viewFlags & ENABLED_MASK) == DISABLED) {
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)) {
switch (event.getAction()) {
// a. 若當前的事件 = 抬起View(主要分析)
case MotionEvent.ACTION_UP:
boolean prepressed = (mPrivateFlags & PREPRESSED) != 0;
...// 經過種種判斷占业,此處省略
// 執(zhí)行performClick() ->>分析1
performClick();
break;
// b. 若當前的事件 = 按下View
case MotionEvent.ACTION_DOWN:
if (mPendingCheckForTap == null) {
mPendingCheckForTap = new CheckForTap();
}
mPrivateFlags |= PREPRESSED;
mHasPerformedLongPress = false;
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
break;
// c. 若當前的事件 = 結束事件(非人為原因)
case MotionEvent.ACTION_CANCEL:
mPrivateFlags &= ~PRESSED;
refreshDrawableState();
removeTapCallback();
break;
// d. 若當前的事件 = 滑動View
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)) {
// Outside button
removeTapCallback();
if ((mPrivateFlags & PRESSED) != 0) {
// Remove any future long press/tap checks
removeLongPressCallback();
// Need to switch from pressed to not pressed
mPrivateFlags &= ~PRESSED;
refreshDrawableState();
}
}
break;
}
// 若該控件可點擊谦疾,就一定返回true
return true;
}
// 若該控件不可點擊犬金,就一定返回false
return false;
}
/**
* 分析1:performClick()
*/
public boolean performClick() {
if (mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
mOnClickListener.onClick(this);
return true;
// 只要我們通過setOnClickListener()為控件View注冊1個點擊事件
// 那么就會給mOnClickListener變量賦值(即不為空)
// 則會往下回調onClick() & performClick()返回true
}
return false;
}
如果View是可點擊的就返回true晚顷,否則返回false。
可以看出onTouch()的執(zhí)行優(yōu)先于onClick()瞳氓,所以一旦在onTouch()消費了事件onClick()就不會執(zhí)行栓袖。
5. 分發(fā)流程
6. 總結
事件分發(fā)從activity的dispatchTouchEvent方法開始往下遍歷子View逐個尋找消費控件,如果有控件或布局消費了此事件恋沃,會執(zhí)行這個控件或布局的onTouchEvent()方法囊咏,到此事件結束,不會再往下傳遞梅割,如果沒有控件或布局消費此事件,view層級從里到外逐個調用onTouchEvent()方法泌类,直到activity自己消費此事件為止底燎。