理~
1、案例
首先我們接著上一篇的代碼,在代碼中添加一個(gè)自定義的LinearLayout:
[java] view plaincopy
package?com.example.zhy_event03;????import?android.content.Context;??import?android.util.AttributeSet;??import?android.util.Log;??import?android.view.MotionEvent;??import?android.widget.LinearLayout;????public?class?MyLinearLayout?extends?LinearLayout??{??????private?static?final?String?TAG?=?MyLinearLayout.class.getSimpleName();????????public?MyLinearLayout(Context?context,?AttributeSet?attrs)??????{??????????super(context,?attrs);??????}????????@Override??????public?boolean?dispatchTouchEvent(MotionEvent?ev)??????{??????????int?action?=?ev.getAction();??????????switch?(action)??????????{??????????case?MotionEvent.ACTION_DOWN:??????????????Log.e(TAG,?"dispatchTouchEvent?ACTION_DOWN");??????????????break;??????????case?MotionEvent.ACTION_MOVE:??????????????Log.e(TAG,?"dispatchTouchEvent?ACTION_MOVE");??????????????break;??????????case?MotionEvent.ACTION_UP:??????????????Log.e(TAG,?"dispatchTouchEvent?ACTION_UP");??????????????break;????????????default:??????????????break;??????????}??????????return?super.dispatchTouchEvent(ev);??????}????????@Override??????public?boolean?onTouchEvent(MotionEvent?event)??????{????????????int?action?=?event.getAction();????????????switch?(action)??????????{??????????case?MotionEvent.ACTION_DOWN:??????????????Log.e(TAG,?"onTouchEvent?ACTION_DOWN");??????????????break;??????????case?MotionEvent.ACTION_MOVE:??????????????Log.e(TAG,?"onTouchEvent?ACTION_MOVE");??????????????break;??????????case?MotionEvent.ACTION_UP:??????????????Log.e(TAG,?"onTouchEvent?ACTION_UP");??????????????break;????????????default:??????????????break;??????????}????????????return?super.onTouchEvent(event);??????}????????@Override??????public?boolean?onInterceptTouchEvent(MotionEvent?ev)??????{????????????????????int?action?=?ev.getAction();??????????switch?(action)??????????{??????????case?MotionEvent.ACTION_DOWN:??????????????Log.e(TAG,?"onInterceptTouchEvent?ACTION_DOWN");??????????????break;??????????case?MotionEvent.ACTION_MOVE:??????????????Log.e(TAG,?"onInterceptTouchEvent?ACTION_MOVE");??????????????break;??????????case?MotionEvent.ACTION_UP:??????????????Log.e(TAG,?"onInterceptTouchEvent?ACTION_UP");??????????????break;????????????default:??????????????break;??????????}????????????????????return?super.onInterceptTouchEvent(ev);??????}????????@Override??????public?void?requestDisallowInterceptTouchEvent(boolean?disallowIntercept)??????{??????????Log.e(TAG,?"requestDisallowInterceptTouchEvent?");??????????super.requestDisallowInterceptTouchEvent(disallowIntercept);??????}????}??
繼承LinearLayout,然后復(fù)寫了與事件分發(fā)機(jī)制有關(guān)的代碼,添加上了日志的打印~
然后看我們的布局文件:
[html] view plaincopy
<com.example.zhy_event03.MyLinearLayout?xmlns:android="http://schemas.android.com/apk/res/android"??????xmlns:tools="http://schemas.android.com/tools"??????android:layout_width="match_parent"??????android:layout_height="match_parent"??????tools:context=".MainActivity"?>????????<com.example.zhy_event03.MyButton??????????android:id="@+id/id_btn"??????????android:layout_width="wrap_content"??????????android:layout_height="wrap_content"??????????android:text="click?me"?/>????</com.example.zhy_event03.MyLinearLayout>??
MyLinearLayout中包含一個(gè)MyButton,MyButton都上篇博客中已經(jīng)出現(xiàn)過,這里就不再貼代碼了,不清楚可以去查看~
然后MainActivity就是直接加載布局,沒有任何代碼~~~
直接運(yùn)行我們的代碼,然后點(diǎn)擊我們的Button,依然是有意的MOVE一下,不然不會觸發(fā)MOVE事件,看一下日志的輸出:
[html] view plaincopy
09-06?09:57:27.287:?E/MyLinearLayout(959):?dispatchTouchEvent?ACTION_DOWN??09-06?09:57:27.287:?E/MyLinearLayout(959):?onInterceptTouchEvent?ACTION_DOWN??09-06?09:57:27.287:?E/MyButton(959):?dispatchTouchEvent?ACTION_DOWN??09-06?09:57:27.297:?E/MyButton(959):?onTouchEvent?ACTION_DOWN??09-06?09:57:27.297:?E/MyButton(959):?onTouchEvent?ACTION_MOVE??09-06?09:57:27.327:?E/MyLinearLayout(959):?dispatchTouchEvent?ACTION_MOVE??09-06?09:57:27.327:?E/MyLinearLayout(959):?onInterceptTouchEvent?ACTION_MOVE??09-06?09:57:27.337:?E/MyButton(959):?dispatchTouchEvent?ACTION_MOVE??09-06?09:57:27.337:?E/MyButton(959):?onTouchEvent?ACTION_MOVE??09-06?09:57:27.457:?E/MyLinearLayout(959):?dispatchTouchEvent?ACTION_UP??09-06?09:57:27.457:?E/MyLinearLayout(959):?onInterceptTouchEvent?ACTION_UP??09-06?09:57:27.457:?E/MyButton(959):?dispatchTouchEvent?ACTION_UP??09-06?09:57:27.457:?E/MyButton(959):?onTouchEvent?ACTION_UP??
可以看到大體的事件流程為:
MyLinearLayout的dispatchTouchEvent ->?MyLinearLayout的onInterceptTouchEvent ->?MyButton的dispatchTouchEvent ->Mybutton的onTouchEvent?
可以看出,在View上觸發(fā)事件,最先捕獲到事件的為View所在的ViewGroup,然后才會到View自身~
下面我們按照日志的輸出,進(jìn)入源碼~
2、源碼分析
ViewGroup -?dispatchTouchEvent
1、ViewGroup -?dispatchTouchEvent - ACTION_DOWN
首先是ViewGroup的dispatchTouchEvent方法:
[java] view plaincopy
@Override?????public?boolean?dispatchTouchEvent(MotionEvent?ev)?{?????????if?(!onFilterTouchEventForSecurity(ev))?{?????????????return?false;?????????}???????????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;?????????????????????????????}????????????????????????????????????????????????????????????????????????????????????????????????????????????????}?????????????????????}?????????????????}?????????????}?????????}??????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????....??
代碼比較長,決定分段貼出,首先貼出的是ACTION_DOWN事件相關(guān)的代碼。
16行:進(jìn)入ACTION_DOWN的處理
17-23行:將mMotionTarget置為null
26行:進(jìn)行判斷:if(disallowIntercept || !onInterceptTouchEvent(ev))
兩種可能會進(jìn)入IF代碼段
1、當(dāng)前不允許攔截,即disallowIntercept =true,
2、當(dāng)前允許攔截但是不攔截,即disallowIntercept =false,但是onInterceptTouchEvent(ev)返回false ;
注:disallowIntercept 可以通過viewGroup.requestDisallowInterceptTouchEvent(boolean);進(jìn)行設(shè)置,后面會詳細(xì)說;而onInterceptTouchEvent(ev)可以進(jìn)行復(fù)寫。
36-57行:開始遍歷所有的子View
41行:進(jìn)行判斷當(dāng)前的x,y坐標(biāo)是否落在子View身上,如果在,47行,執(zhí)行child.dispatchTouchEvent(ev),就進(jìn)入了View的dispatchTouchEvent代碼中了,如果不了解請參考:Android View的事件分發(fā)機(jī)制,當(dāng)child.dispatchTouchEvent(ev)返回true,則為mMotionTarget=child;然后return true;
ViewGroup的ACTION_DOWN分析結(jié)束,總結(jié)一下:
ViewGroup實(shí)現(xiàn)捕獲到DOWN事件,如果代碼中不做TOUCH事件攔截,則開始查找當(dāng)前x,y是否在某個(gè)子View的區(qū)域內(nèi),如果在,則把事件分發(fā)下去。
按照日志,接下來到達(dá)ACTION_MOVE
2、ViewGroup -?dispatchTouchEvent - ACTION_MOVE
首先我們源碼進(jìn)行刪減,只留下MOVE相關(guān)的代碼:
[java] view plaincopy
@Override?????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;??????????????????????????????????????????final?View?target?=?mMotionTarget;????????????????????????????????????if?(!disallowIntercept?&&?onInterceptTouchEvent(ev))?{??????????????????????}???????????????????????????final?float?xc?=?scrolledXFloat?-?(float)?target.mLeft;?????????final?float?yc?=?scrolledYFloat?-?(float)?target.mTop;?????????ev.setLocation(xc,?yc);???????????return?target.dispatchTouchEvent(ev);?????}??
18行:把ACTION_DOWN時(shí)賦值的mMotionTarget,付給target ;?
23行:if (!disallowIntercept && onInterceptTouchEvent(ev)) 當(dāng)前允許攔截且攔截了,才進(jìn)入IF體,當(dāng)然了默認(rèn)是不會攔截的~這里執(zhí)行了onInterceptTouchEvent(ev)
28-30行:把坐標(biāo)系統(tǒng)轉(zhuǎn)化為子View的坐標(biāo)系統(tǒng)
32行:直接return?target.dispatchTouchEvent(ev);?
可以看到,正常流程下,ACTION_MOVE在檢測完是否攔截以后,直接調(diào)用了子View.dispatchTouchEvent,事件分發(fā)下去;
最后就是ACTION_UP了
3、ViewGroup -?dispatchTouchEvent - ACTION_UP
[java] view plaincopy
public?boolean?dispatchTouchEvent(MotionEvent?ev)?{?????????if?(!onFilterTouchEventForSecurity(ev))?{?????????????return?false;?????????}???????????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)?{...}????boolean?isUpOrCancel?=?(action?==?MotionEvent.ACTION_UP)?||?????????????????(action?==?MotionEvent.ACTION_CANCEL);????if?(isUpOrCancel)?{?????????????mGroupFlags?&=?~FLAG_DISALLOW_INTERCEPT;?????????}??final?View?target?=?mMotionTarget;??if(target?==null?){...}??if?(!disallowIntercept?&&?onInterceptTouchEvent(ev))?{...}???????????if?(isUpOrCancel)?{?????????????mMotionTarget?=?null;?????????}?????????????????????????????final?float?xc?=?scrolledXFloat?-?(float)?target.mLeft;?????????final?float?yc?=?scrolledYFloat?-?(float)?target.mTop;?????????ev.setLocation(xc,?yc);???????????return?target.dispatchTouchEvent(ev);?????}??
17行:判斷當(dāng)前是否是ACTION_UP
21,28行:分別重置攔截標(biāo)志位以及將DOWN賦值的mMotionTarget置為null,都UP了,當(dāng)然置為null,下一次DOWN還會再賦值的~
最后,修改坐標(biāo)系統(tǒng),然后調(diào)用target.dispatchTouchEvent(ev);
正常情況下,即我們上例整個(gè)代碼的流程我們已經(jīng)走完了:
1、ACTION_DOWN中,ViewGroup捕獲到事件,然后判斷是否攔截,如果沒有攔截,則找到包含當(dāng)前x,y坐標(biāo)的子View,賦值給mMotionTarget,然后調(diào)用 mMotionTarget.dispatchTouchEvent
2、ACTION_MOVE中,ViewGroup捕獲到事件,然后判斷是否攔截,如果沒有攔截,則直接調(diào)用mMotionTarget.dispatchTouchEvent(ev)
3、ACTION_UP中,ViewGroup捕獲到事件,然后判斷是否攔截,如果沒有攔截,則直接調(diào)用mMotionTarget.dispatchTouchEvent(ev)
當(dāng)然了在分發(fā)之前都會修改下坐標(biāo)系統(tǒng),把當(dāng)前的x,y分別減去child.left 和 child.top ,然后傳給child;
3、關(guān)于攔截
1、如何攔截
上面的總結(jié)都是基于:如果沒有攔截;那么如何攔截呢?
復(fù)寫ViewGroup的onInterceptTouchEvent方法:
[java] view plaincopy
@Override??????public?boolean?onInterceptTouchEvent(MotionEvent?ev)??????{??????????int?action?=?ev.getAction();??????????switch?(action)??????????{??????????case?MotionEvent.ACTION_DOWN:????????????????????????????return?true?;???????????case?MotionEvent.ACTION_MOVE:????????????????????????????return?true?;???????????case?MotionEvent.ACTION_UP:????????????????????????????return?true?;???????????}????????????????????return?false;??????}??
默認(rèn)是不攔截的,即返回false;如果你需要攔截,只要return true就行了,這要該事件就不會往子View傳遞了,并且如果你在DOWN retrun true ,則DOWN,MOVE,UP子View都不會捕獲事件;如果你在MOVE return true , 則子View在MOVE和UP都不會捕獲事件。
原因很簡單,當(dāng)onInterceptTouchEvent(ev) return true的時(shí)候,會把mMotionTarget 置為null ;?
2、如何不被攔截
如果ViewGroup的onInterceptTouchEvent(ev) 當(dāng)ACTION_MOVE時(shí)return true ,即攔截了子View的MOVE以及UP事件;
此時(shí)子View希望依然能夠響應(yīng)MOVE和UP時(shí)該咋辦呢?
Android給我們提供了一個(gè)方法:requestDisallowInterceptTouchEvent(boolean) 用于設(shè)置是否允許攔截,我們在子View的dispatchTouchEvent中直接這么寫:
[java] view plaincopy
@Override??????public?boolean?dispatchTouchEvent(MotionEvent?event)??????{??????????getParent().requestDisallowInterceptTouchEvent(true);????????????int?action?=?event.getAction();????????????switch?(action)??????????{??????????case?MotionEvent.ACTION_DOWN:??????????????Log.e(TAG,?"dispatchTouchEvent?ACTION_DOWN");??????????????break;??????????case?MotionEvent.ACTION_MOVE:??????????????Log.e(TAG,?"dispatchTouchEvent?ACTION_MOVE");??????????????break;??????????case?MotionEvent.ACTION_UP:??????????????Log.e(TAG,?"dispatchTouchEvent?ACTION_UP");??????????????break;????????????default:??????????????break;??????????}??????????return?super.dispatchTouchEvent(event);??????}??
getParent().requestDisallowInterceptTouchEvent(true); ?這樣即使ViewGroup在MOVE的時(shí)候return true,子View依然可以捕獲到MOVE以及UP事件。
從源碼也可以解釋:
ViewGroup MOVE和UP攔截的源碼是這樣的:
[java] view plaincopy
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;??????????}??
當(dāng)我們把disallowIntercept設(shè)置為true時(shí),!disallowIntercept直接為false,于是攔截的方法體就被跳過了~
注:如果ViewGroup在onInterceptTouchEvent(ev) ?ACTION_DOWN里面直接return true了,那么子View是木有辦法的捕獲事件的~~~
4、如果沒有找到合適的子View
我們的實(shí)例,直接點(diǎn)擊ViewGroup內(nèi)的按鈕,當(dāng)然直接很順利的走完整個(gè)流程;
但是有兩種特殊情況
1、ACTION_DOWN的時(shí)候,子View.dispatchTouchEvent(ev)返回的為false ;?
如果你仔細(xì)看了,你會注意到ViewGroup的dispatchTouchEvent(ev)的ACTION_DOWN代碼是這樣的
[java] view plaincopy
if?(child.dispatchTouchEvent(ev))??{????????????????????????????????????????????????????????????????mMotionTarget?=?child;????????????????????????????????return?true;????????????????????????????}??
只有在child.dispatchTouchEvent(ev)返回true了,才會認(rèn)為找到了能夠處理當(dāng)前事件的View,即mMotionTarget = child;
但是如果返回false,那么mMotionTarget 依然是null
mMotionTarget 為null會咋樣呢?
其實(shí)ViewGroup也是View的子類,如果沒有找到能夠處理該事件的子View,或者干脆就沒有子View;
那么,它作為一個(gè)View,就相當(dāng)于View的事件轉(zhuǎn)發(fā)了~~直接super.dispatchTouchEvent(ev);
源碼是這樣的:
[java] view plaincopy
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);?????????}??
我們沒有一個(gè)能夠處理該事件的目標(biāo)元素,意味著我們需要自己處理~~~就相當(dāng)于傳統(tǒng)的View~
2、那么什么時(shí)候子View.dispatchTouchEvent(ev)返回的為true
如果你仔細(xì)看了上篇博客,你會發(fā)現(xiàn)只要子View支持點(diǎn)擊或者長按事件一定返回true~~
源碼是這樣的:
[java] view plaincopy
???if?(((viewFlags?&?CLICKABLE)?==?CLICKABLE?||??????????????????(viewFlags?&?LONG_CLICKABLE)?==?LONG_CLICKABLE))?{??????????????????return?true?;???????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????}??
5、總結(jié)
關(guān)于代碼流程上面已經(jīng)總結(jié)過了~
1、如果ViewGroup找到了能夠處理該事件的View,則直接交給子View處理,自己的onTouchEvent不會被觸發(fā);
2、可以通過復(fù)寫onInterceptTouchEvent(ev)方法,攔截子View的事件(即return true),把事件交給自己處理,則會執(zhí)行自己對應(yīng)的onTouchEvent方法
3、子View可以通過調(diào)用getParent().requestDisallowInterceptTouchEvent(true); ?阻止ViewGroup對其MOVE或者UP事件進(jìn)行攔截;
好了,那么實(shí)際應(yīng)用中能解決哪些問題呢?
比如你需要寫一個(gè)類似slidingmenu的左側(cè)隱藏menu,主Activity上有個(gè)Button、ListView或者任何可以響應(yīng)點(diǎn)擊的View,你在當(dāng)前View上死命的滑動,菜單欄也出不來;因?yàn)镸OVE事件被子View處理了~ 你需要這么做:在ViewGroup的dispatchTouchEvent中判斷用戶是不是想顯示菜單,如果是,則在onInterceptTouchEvent(ev)攔截子View的事件;自己進(jìn)行處理,這樣自己的onTouchEvent就可以順利展現(xiàn)出菜單欄了~~
總結(jié)
以上是生活随笔為你收集整理的Android ViewGroup事件分发机制的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。