參考郭霖博客:
http://blog.csdn.net/guolin_blog/article/details/9097463
http://blog.csdn.net/guolin_blog/article/details/9153747
標(biāo)簽(空格分隔): Android
onTouch與onClick的關(guān)系,調(diào)用時(shí)機(jī)###
button.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Log.d("TAG", "onClick execute");
}
});
button.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
Log.d("TAG", "onTouch execute, action " + event.getAction());
return false;
}
});
onTouch是優(yōu)先于onClick執(zhí)行的,并且onTouch執(zhí)行了兩次,一次是ACTION_DOWN,一次是ACTION_UP(你還可能會(huì)有多次ACTION_MOVE的執(zhí)行,如果你手抖了一下)。因此事件傳遞的順序是先經(jīng)過(guò)onTouch,再傳遞到onClick。
【要注意的是onTouch與onTouchEvent都可以監(jiān)控ACTION_DOWN、ACTION_MOVE、ACTION_UP等手勢(shì),而且是持續(xù)監(jiān)聽,即每個(gè)ACTION都會(huì)進(jìn)入這兩個(gè)方法進(jìn)行監(jiān)聽】
結(jié)論:onTouch方法是有返回值的,這里我們返回的是false,那么onClick()還會(huì)執(zhí)行,如果我們嘗試把onTouch方法里的返回值改成true,那么onClick()就不會(huì)執(zhí)行
應(yīng)用場(chǎng)景:為什么給ListView引入了一個(gè)滑動(dòng)菜單的功能,ListView就不能滾動(dòng)了?
滑動(dòng)菜單的功能是通過(guò)給ListView注冊(cè)了一個(gè)touch事件來(lái)實(shí)現(xiàn)的。如果你在onTouch方法里處理完了滑動(dòng)邏輯后返回true,那么ListView本身的滾動(dòng)事件就被屏蔽了,自然也就無(wú)法滑動(dòng)(原理同前面例子中按鈕不能點(diǎn)擊),因此解決辦法就是在onTouch方法里返回false。
滾動(dòng)事件也像點(diǎn)擊事件一樣,跟onTouch的返回值有關(guān)???
【任何控件本身是沒有dispatchTouchEvent方法的,是從view類繼承的】
首先你需要知道一點(diǎn),只要你觸摸到了任何一個(gè)控件,首先會(huì)去調(diào)用該控件所在布局的dispatchTouchEvent方法【該dispatchTouchEvent方法是繼承于ViewGroup】,然后在布局的dispatchTouchEvent方法中找到被點(diǎn)擊的相應(yīng)控件,再去調(diào)用該控件的dispatchTouchEvent方法。所有view控件的dispatchTouchEvent方法都是繼承于view,示意圖如下:
單個(gè)子View時(shí)

布局嵌套時(shí)

子View的dispatchTouchEvent方法的源碼:
public boolean dispatchTouchEvent(MotionEvent event) {
if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
mOnTouchListener.onTouch(this, event)) {
return true;
}
return onTouchEvent(event);
}
//條件一:mOnTouchListener正是在setOnTouchListener方法里賦值的,也就是說(shuō)只要我們給控件注冊(cè)了touch事件,mOnTouchListener就一定被賦值了。
//條件二:(mViewFlags & ENABLED_MASK) == ENABLED是判斷當(dāng)前點(diǎn)擊的控件是否是enable的,按鈕默認(rèn)都是enable的
//條件三:mOnTouchListener.onTouch(this, event),其實(shí)也就是去回調(diào)控件注冊(cè)touch事件時(shí)的onTouch方法。
讓這三個(gè)條件全部成立,從而dispatchTouchEvent方法直接返回true
而且onClick的調(diào)用肯定是在onTouchEvent(event)方法中的,所以當(dāng)在onTouch方法里返回了true【而且既然可以調(diào)用onTouch方法,那么就一定滿足該控件注冊(cè)了touch事件,而且是可以點(diǎn)擊的】,就會(huì)讓dispatchTouchEvent方法直接返回true,所以不會(huì)走return onTouchEvent(event),當(dāng)然就不會(huì)調(diào)用到onClick方法了
1. onTouch和onTouchEvent有什么區(qū)別,又該如何使用?
從源碼中可以看出,這兩個(gè)方法都是在View的dispatchTouchEvent中調(diào)用的,onTouch優(yōu)先于onTouchEvent執(zhí)行。如果在onTouch方法中通過(guò)返回true將事件消費(fèi)掉,onTouchEvent將不會(huì)再執(zhí)行。
另外需要注意的是,onTouch能夠得到執(zhí)行需要兩個(gè)前提條件,第一mOnTouchListener的值不能為空,第二當(dāng)前點(diǎn)擊的控件必須是enable的。因此如果你有一個(gè)控件是非enable的,那么給它注冊(cè)onTouch事件將永遠(yuǎn)得不到執(zhí)行。對(duì)于這一類控件,如果我們想要監(jiān)聽它的touch事件,就必須通過(guò)在該控件中重寫onTouchEvent方法來(lái)實(shí)現(xiàn)。
因?yàn)?code>onTouchEvent()方法的代碼有點(diǎn)長(zhǎng),所以就不列出來(lái)了,在onTouchEvent()可以判斷ACTION_DOWN、ACTION_MOVE、ACTION_UP等手勢(shì)。還有里面的performClick()會(huì)調(diào)用到onClick()
public boolean performClick() {
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
if (mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
mOnClickListener.onClick(this);
return true;
}
return false;
}
//mOnClickListener不是null,就會(huì)去調(diào)用它的onClick方法
而mOnClickListener是在這里賦值的
public void setOnClickListener(OnClickListener l) {
if (!isClickable()) {
setClickable(true);
}
mOnClickListener = l;
}
touch事件的ACTION層級(jí)傳遞###
我們都知道如果給一個(gè)控件注冊(cè)了touch事件,每次點(diǎn)擊它的時(shí)候都會(huì)觸發(fā)一系列的ACTION_DOWN,ACTION_MOVE,ACTION_UP等事件。這里需要注意,如果你在執(zhí)行ACTION_DOWN的時(shí)候返回了false,后面一系列其它的action就不會(huì)再得到執(zhí)行了。簡(jiǎn)單的說(shuō),就是當(dāng)dispatchTouchEvent在進(jìn)行事件分發(fā)的時(shí)候,只有前一個(gè)action返回true,才會(huì)觸發(fā)后一個(gè)action。
這里要補(bǔ)充一句,即使在onTouch事件里面返回了false。所以就一定會(huì)進(jìn)入到onTouchEvent方法中其中有一個(gè)if用于判斷是否可以點(diǎn)擊,如果可以點(diǎn)擊則返回一個(gè)true。是不是有一種被欺騙的感覺?明明在onTouch事件里返回了false,系統(tǒng)還是在onTouchEvent方法中幫你返回了true。就因?yàn)檫@個(gè)原因,才使得ACTION_UP可以得到執(zhí)行。
所以將按鈕替換成ImageView,然后給它也注冊(cè)一個(gè)touch事件,并返回false。在ACTION_DOWN執(zhí)行完后,后面的一系列action都不會(huì)得到執(zhí)行了。因?yàn)镮mageView和按鈕不同,它是默認(rèn)不可點(diǎn)擊的,所以不能進(jìn)入onTouchEvent方法中其中的那個(gè)用于判斷是否可以點(diǎn)擊的if,直接在onTouchEvent中返回false,所以最終的返回還是false,所以就導(dǎo)致后面其它的action都無(wú)法執(zhí)行了。
應(yīng)用場(chǎng)景:為什么圖片輪播器里的圖片使用Button而不用ImageView?
主要就是因?yàn)锽utton是可點(diǎn)擊的,而ImageView是不可點(diǎn)擊的。如果想要使用ImageView,可以有兩種改法。第一,在ImageView的onTouch方法里返回true,這樣可以保證ACTION_DOWN之后的其它action都能得到執(zhí)行,才能實(shí)現(xiàn)圖片滾動(dòng)的效果。第二,在布局文件里面給ImageView增加一個(gè)android:clickable="true"的屬性,這樣ImageView變成可點(diǎn)擊的之后,即使在onTouch里返回了false,ACTION_DOWN之后的其它action也是可以得到執(zhí)行的。
布局嵌套時(shí)的事件分發(fā)##
分發(fā)順序:
Acivity->Window->DecorView->ViewGroup->View
ViewGroup中有一個(gè)onInterceptTouchEvent方法
ViewGroup中有一個(gè)dispatchTouchEvent方法
public boolean dispatchTouchEvent(MotionEvent ev) {
final int action = ev.getAction();
final float xf = ev.getX();
final float yf = ev.getY();
final float scrolledXFloat = xf + mScrollX;
final float scrolledYFloat = yf + mScrollY;
final Rect frame = mTempRect;
boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (action == MotionEvent.ACTION_DOWN) {
if (mMotionTarget != null) {
mMotionTarget = null;
}
if (disallowIntercept || !onInterceptTouchEvent(ev)) {
ev.setAction(MotionEvent.ACTION_DOWN);
final int scrolledXInt = (int) scrolledXFloat;
final int scrolledYInt = (int) scrolledYFloat;
final View[] children = mChildren;
final int count = mChildrenCount;
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);
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;
if (child.dispatchTouchEvent(ev)) {
mMotionTarget = child;
return true;
}
}
}
}
}
}
boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) ||
(action == MotionEvent.ACTION_CANCEL);
if (isUpOrCancel) {
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
}
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);
}
if (!disallowIntercept && onInterceptTouchEvent(ev)) {
final float xc = scrolledXFloat - (float) target.mLeft;
final float yc = scrolledYFloat - (float) target.mTop;
mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
ev.setAction(MotionEvent.ACTION_CANCEL);
ev.setLocation(xc, yc);
if (!target.dispatchTouchEvent(ev)) {
}
mMotionTarget = null;
return true;
}
if (isUpOrCancel) {
mMotionTarget = null;
}
final float xc = scrolledXFloat - (float) target.mLeft;
final float yc = scrolledYFloat - (float) target.mTop;
ev.setLocation(xc, yc);
if ((target.mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {
ev.setAction(MotionEvent.ACTION_CANCEL);
target.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
mMotionTarget = null;
}
return target.dispatchTouchEvent(ev);
}
//在ViewGroup中的dispatchTouchEvent方法,第13行可以看到一個(gè)條件判斷,如果disallowIntercept和!onInterceptTouchEvent(ev)兩者有一個(gè)為true,就會(huì)進(jìn)入到這個(gè)條件判斷中。
//disallowIntercept是指是否禁用掉事件攔截的功能,默認(rèn)是false,也可以通過(guò)調(diào)用requestDisallowInterceptTouchEvent方法對(duì)這個(gè)值進(jìn)行修改。那么當(dāng)?shù)谝粋€(gè)值為false的時(shí)候就會(huì)完全依賴第二個(gè)值來(lái)決定是否可以進(jìn)入到條件判斷的內(nèi)部,第二個(gè)值是什么呢?竟然就是對(duì)onInterceptTouchEvent方法的返回值取反!也就是說(shuō)如果我們?cè)趏nInterceptTouchEvent方法中返回false,就會(huì)讓第二個(gè)值為true,從而進(jìn)入到條件判斷的內(nèi)部,如果我們?cè)趏nInterceptTouchEvent方法中返回true,就會(huì)讓第二個(gè)值為false,從而跳出了這個(gè)條件判斷。
//而子view的時(shí)事件分發(fā)就是在這個(gè)條件判斷中,這個(gè)條件判斷的內(nèi)部是怎么實(shí)現(xiàn)的?在第19行通過(guò)一個(gè)for循環(huán),遍歷了當(dāng)前ViewGroup下的所有子View,然后在第24行判斷當(dāng)前遍歷的View是不是正在點(diǎn)擊的View,如果是的話就會(huì)進(jìn)入到該條件判斷的內(nèi)部,然后在第29行調(diào)用了該View的dispatchTouchEvent,之后的流程就跟不嵌套時(shí)的子view事件分發(fā)一樣。
//所以說(shuō)當(dāng)跳出了這個(gè)條件判斷后,即在onInterceptTouchEvent方法中返回true,就會(huì)讓第二個(gè)值!onInterceptTouchEvent為false子View的事件分發(fā)將得不到執(zhí)行,所以子View就會(huì)被屏蔽。
//而當(dāng)子View沒有被屏蔽時(shí),即子View的dispatchTouchEvent得到執(zhí)行,而子View是可點(diǎn)擊的,子View的dispatchTouchEvent一定返回True,就會(huì)導(dǎo)致ViewGroup的dispatchTouchEvent第29行的條件判斷成立,于是在第31行給ViewGroup的dispatchTouchEvent方法直接返回了true。這樣就導(dǎo)致后面的代碼無(wú)法執(zhí)行到了,所以導(dǎo)致后面的代碼中ViewGroup的touch事件就沒有辦法執(zhí)行了
//
而當(dāng)我們點(diǎn)擊的只是ViewGroup的空白區(qū)域而不是子View時(shí),首先也是會(huì)進(jìn)入ViewGroup的dispatchTouchEvent,就算ViewGroup的onInterceptTouchEvent返回的是true,令ViewGroup的dispatchTouchEvent中的13行的條件判斷的!onInterceptTouchEvent變成false,不進(jìn)入這個(gè)條件判斷里面,即攔截子View的事件。但是這時(shí)點(diǎn)擊的不是子View控件,所以不會(huì)在dispatchTouchEvent的31行返回true,令方法立馬結(jié)束,不會(huì)使ViewGroup的touch事件沒有執(zhí)行

***結(jié)論:
要想攔截子View的事件,就重寫ViewGroup的onInterceptTouchEvent方法,使其返回true。***默認(rèn)是false不攔截子View事件
而且子View沒有onInterceptTouchEvent
如果ViewGroup的onInterceptTouchEvent方法,使其返回true即攔截子View事件,那么在攔截了同一序列事件中的ACTION_DOWN后,onInterceptTouchEvent不會(huì)在被調(diào)用,并且繼續(xù)攔截剩下的ACTION_MOVE,ACTION_UP
另外注意下如果子View設(shè)置了FLAG_DISALLOW_INTERCEPT這個(gè)標(biāo)記位,具體看一下《開發(fā)藝術(shù)探索》,可以讓ViewGroup只能攔截ACTION_DOWN,不能攔截ACTION_MOVE,ACTION_UP
攔截方式有外部攔截法于內(nèi)部攔截法,一般推薦使用較為簡(jiǎn)單的外部攔截法
所有函數(shù)的返回值都是顯而易見的,True:攔截、消費(fèi)處理。False:不攔截、不消費(fèi)處理。只有onTouch()的返回值是怪怪的,返回的是false的時(shí)候onClick()才會(huì)執(zhí)行!?。?/p>
事件消費(fèi)傳遞###
如果子View的onTouchEvent返回的是false,那么就會(huì)交給他的父容器onTouchEvent處理,如果所有的元素都不處理這個(gè)事件的話,就會(huì)交給Activity的onTouchEvent處理。
只要找到了事件處理者的話,只要當(dāng)前ACTION會(huì)有去找處理者的過(guò)程,而之后的每個(gè)ACTION會(huì)直接被之前找到的處理者消費(fèi)掉,不會(huì)有那個(gè)找處理者的那個(gè)查找過(guò)程。
【那onTouch的返回值有對(duì)消費(fèi)傳遞有影響嗎?】
答案是有影響的,即使onTouch的返回值是false,就會(huì)調(diào)用onTouchEvent,所以事件傳遞消費(fèi)還是要看onTouchEvent;如果onTouch的返回值是true的話,事件就會(huì)被處理掉,而且onTouchEvent不會(huì)被調(diào)用。
【在onTouchEvent的返回值指的是每個(gè)case判斷中對(duì)每個(gè)ACTION的處理后的返回值,還是指onTouchEvent最底下的返回值?】
一般指的是每個(gè)case判斷中對(duì)每個(gè)ACTION的處理后的返回值,但是onTouchEvent最底下的返回值是必須要寫的,因?yàn)楹瘮?shù)必須要有返回值。
View的onTouchEvent默認(rèn)返回的是true,即處理消費(fèi)事件