Android事件分發(fā)分為View和ViewGroup的分發(fā)悯恍,由dispatchTouchEvent,onInterceptTouchEvent钙畔,onTouchEvent三個方法共同完成分發(fā)過程柿祈。
View的事件分發(fā)
View的事件分發(fā)由dispatchTouchEvent,onTouchEvent兩個方法完成蔓姚。
我們先來看一下一個完整事件的執(zhí)行流程。
在頁面上有一個TestTextView(繼承自TextView)慨丐。為了方便查看結(jié)果坡脐,在TestTextView中的dispatchTouchEvent,onTouchEvent方法中打了Log日志
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
Log.e("tag","===== onTouchEvent "+ event.getAction());
break;
case MotionEvent.ACTION_UP:
Log.e("tag","===== onTouchEvent " + event.getAction());
break;
case MotionEvent.ACTION_MOVE:
Log.e("tag","===== onTouchEvent " + event.getAction());
break;
}
return super.onTouchEvent(event);
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
Log.e("tag","===== dispatchTouchEvent " + event.getAction());
break;
case MotionEvent.ACTION_UP:
Log.e("tag","===== dispatchTouchEvent " + event.getAction());
break;
case MotionEvent.ACTION_MOVE:
Log.e("tag","===== dispatchTouchEvent " + event.getAction());
break;
}
return super.dispatchTouchEvent(event);
}
設(shè)置TestTextView的點擊事件和touch監(jiān)聽
tv_touch.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Log.e("tag","======== onClick");
}
});
tv_touch.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
Log.e("tag","===== onTouch "+ event.getAction());
break;
case MotionEvent.ACTION_UP:
Log.e("tag","===== onTouch " + event.getAction());
break;
case MotionEvent.ACTION_MOVE:
Log.e("tag","===== onTouch " + event.getAction());
break;
}
return false;
}
});
點擊TextView查看Log日志:
11-24 16:20:46.870 10998-10998/com.zy.touchevent E/tag: ===== dispatchTouchEvent 0
11-24 16:20:46.871 10998-10998/com.zy.touchevent E/tag: ===== onTouch 0
11-24 16:20:46.871 10998-10998/com.zy.touchevent E/tag: ===== onTouchEvent 0
11-24 16:20:46.897 10998-10998/com.zy.touchevent E/tag: ===== dispatchTouchEvent 1
11-24 16:20:46.897 10998-10998/com.zy.touchevent E/tag: ===== onTouch 1
11-24 16:20:46.897 10998-10998/com.zy.touchevent E/tag: ===== onTouchEvent 1
11-24 16:20:46.898 10998-10998/com.zy.touchevent E/tag: ===== onClick
從Log日志上可以看出房揭,點擊頁面上的一個View备闲,事件傳遞的方法順序是 dispatchTouchEvent ->onTouch ->onTouchEvent->onClick 晌端;
先看dispatchTouchEvent 方法
/**
* 分發(fā)事件,因為是view的事件分發(fā)此時分發(fā)的對象是自己
* 返回值是boolean類型,表示是否消費當(dāng)前事件
* @param event
* @return
*/
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
看一下dispatchTouchEvent的源碼:
boolean result = false;//返回結(jié)果浅役,默認(rèn)false不處理
...
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
//前面如果沒有處理斩松,result為false,此時result結(jié)果受onTouchEvent
if (!result && onTouchEvent(event)) {
result = true;
}
從上面代碼可以看出觉既,result默認(rèn)為false不處理惧盹,且result的結(jié)果在兩個if中確定。
第一個if判斷 li != null
在if上面已經(jīng)初始化瞪讼,li!=null
成立钧椰;
mOnTouchListener 在我們設(shè)置setOnTouchListener
監(jiān)聽的時候已經(jīng)賦值,也不為null符欠;
(mViewFlags & ENABLED_MASK) == ENABLED
判斷當(dāng)前點擊的控件是否是enable的嫡霞,按鈕默認(rèn)都是enable的,成立希柿;
最后一個條件li.mOnTouchListener.onTouch(this, event)
的返回值受OnTouchListener事件中的onTouch()方法影響诊沪,如果在onTouch()中返回ture則result=true
成立,就不會執(zhí)行第二個if語句曾撤。
第二個if判斷當(dāng)?shù)谝粋€if判斷不成立時端姚,此時result的結(jié)果受自身onTouchEvent影響;
既然dispatchTouchEvent的返回值受onTouch()方法影響挤悉,onTouch()的默認(rèn)返回值為false渐裸,我們修改為true看看此時的打印日志:
11-24 17:32:13.263 18052-18052/com.zy.touchevent E/tag: ===== dispatchTouchEvent 0
11-24 17:32:13.264 18052-18052/com.zy.touchevent E/tag: ===== onTouch 0
11-24 17:32:13.316 18052-18052/com.zy.touchevent E/tag: ===== dispatchTouchEvent 1
11-24 17:32:13.316 18052-18052/com.zy.touchevent E/tag: ===== onTouch 1
從Log日志我們可以發(fā)現(xiàn)onTouchEvent,onclick方法沒了,這很好理解装悲。因為onTouch 方法返回true表示將此次事件消費掉昏鹃,而onTouchEvent方法在onTouch 后面執(zhí)行,事件自然也就執(zhí)行不到onTouchEvent和onclick诀诊。
既然onTouch 方法能夠影響onTouchEvent的執(zhí)行洞渤,那么onTouchEvent又和onclick有沒有一腿呢?
來看看onTouchEvent方法的源碼(源碼有點多属瓣,省略和onclick不相關(guān)部分)
public boolean onTouchEvent(MotionEvent event) {
switch (action) {
case MotionEvent.ACTION_UP:
if (!post(mPerformClick)) {
performClick();
}
break;
}
}
public boolean performClick() {
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;
}
當(dāng)手指抬起時您宪,performClick();
方法會被調(diào)用,點擊查看方法奠涌,可以知道當(dāng)li.mOnClickListener != null
不為空時,li.mOnClickListener.onClick(this);
view的點擊方法就會被調(diào)用。
View的事件分發(fā)就說到這里了磷杏,下面來說說ViewGroup的事件分發(fā):
ViewGroup的事件分發(fā)
我覺得這張圖是最能簡單說明ViewGroup的事件分發(fā)順序了溜畅,所以我就借鑒一下 ?
當(dāng)一個事件(點擊或者touch)到達activity的根節(jié)點也就是ViewGroup時,ViewGroup會把事件遍歷分發(fā)給它包含的每個子View,而當(dāng)子View為ViewGroup時极祸,又會通過調(diào)用ViwGroup的dispatchTouchEvent方法繼續(xù)調(diào)用其內(nèi)部的View的dispatchTouchEvent方法慈格。所以上圖的分發(fā)順序是① -- ② -- ⑤ -- ⑥ -- ⑦ -- ③ -- ④怠晴。如果在分發(fā)過程中任何一個子View的dispatchTouchEvent的返回值為true(消費事件),后面的分發(fā)都會中止。
看一下ViewGroup的源碼吧浴捆,很長蒜田,會省略很多,我開始看不懂也是參考別人的講解看的选泻,文章末尾會給鏈接的冲粤。
1、dispatchTouchEvent方法
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
...
if (disallowIntercept || !onInterceptTouchEvent(ev)) { ①
...
for (int i = count - 1; i >= 0; i--) {
final View child = children[i];
if (child.dispatchTouchEvent(ev)) { ②
mMotionTarget = child;
return true;
}
}
}
return target.dispatchTouchEvent(ev);
}
這里只給了幾處影響dispatchTouchEvent方法返回值的代碼页眯,第一個判斷里面兩個條件滿足其中一個就會進入后面代碼梯捕,disallowIntercept是指是否禁用掉事件攔截的功能,默認(rèn)是false窝撵,也可以通過調(diào)用requestDisallowInterceptTouchEvent方法對這個值進行修改傀顾。第二個判斷是對onInterceptTouchEvent(攔截事件)返回值取反。如果一個控件是可點擊的碌奉,那么點擊該控件時短曾,dispatchTouchEvent的返回值必定是true,第二個判斷條件child.dispatchTouchEvent(ev)為true成立赐劣。
ViewGroup也是View的子類嫉拐,因此View的事件分發(fā)在ViewGroup中同樣適用。當(dāng)ViewGroup中沒有子View時隆豹,走的就是View的分發(fā)過程了
final View target = mMotionTarget;
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);
}
2椭岩、onInterceptTouchEvent方法
public boolean onInterceptTouchEvent(MotionEvent ev) {
return false;
}
上面就是onInterceptTouchEvent方法的源碼,默認(rèn)返回false不攔截璃赡,我們可以在需要的時候進行攔截判哥。
3、onTouchEvent方法
剛剛也已經(jīng)說了碉考,ViewGroup也是View的一個子類塌计。因此當(dāng)ViewGroup沒有子View時onTouchEvent方法會被調(diào)用,還有就是子View的onTouchEvent方法返回值為false侯谁,父容器的onTouchEvent方法會被調(diào)用锌仅。
我在開發(fā)藝術(shù)探索里看見了一個很好理解的比喻:假如點擊事件是一個難題,這個難題被上級領(lǐng)導(dǎo)分配給一個程序員處理(類似事件分發(fā)過程)墙贱,結(jié)果這個程序員搞不定(onTouchEvent返回了false)热芹,但是難題又必須要解決,那就只能交給水平更高的上級解決了(上級的onTouchEvent方法被調(diào)用)惨撇,如果上級在搞不定伊脓,那只能交給上級的上級去解決,就這樣將難題一層層上拋魁衙。
最后關(guān)于事件分發(fā)的總結(jié):
1报腔、Touch事件分發(fā)中只有兩個主角:ViewGroup和View株搔。包含onInterceptTouchEvent、
dispatchTouchEvent纯蛾、onTouchEvent三個相關(guān)事件纤房,其中ViewGroup又繼承于View。
2翻诉、一個點擊事件產(chǎn)生后炮姨,他的傳遞過程遵循如下順序: Activity -> Window -> View
3、觸摸事件由Action_Down米丘、Action_Move剑令、Aciton_UP組成,其中一次完整的觸摸事件中拄查,Down和Up都只
有一個吁津,Move有若干個,可以為0個堕扶。
總結(jié):第一次寫這種總結(jié)分析類的文章碍脏,還是很困難的,首先我沒有一個貫穿全文的思路(想了幾個稍算,被寫死了)典尾,然后就是不知道該怎么寫(雖然我心里明白是啥回事,但說不出來啊/(ㄒoㄒ)/)糊探,所以這篇文章里面我借鑒了好多別人的東西钾埂,包括思路,代碼片段(希望不會被瞧不起)科平。哈哈褥紫,雖然寫的不好,但還是有收獲的瞪慧,繼續(xù)加油~
參考:
《安卓開發(fā)藝術(shù)探索》
Android事件分發(fā)機制完全解析髓考,帶你從源碼的角度徹底理解(下)
Android:30分鐘弄明白Touch事件分發(fā)機制