理~
1、案例
首先我們接著上一篇的代碼,在代碼中添加一個自定義的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,然后復寫了與事件分發機制有關的代碼,添加上了日志的打印~
然后看我們的布局文件:
[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中包含一個MyButton,MyButton都上篇博客中已經出現過,這里就不再貼代碼了,不清楚可以去查看~
然后MainActivity就是直接加載布局,沒有任何代碼~~~
直接運行我們的代碼,然后點擊我們的Button,依然是有意的MOVE一下,不然不會觸發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上觸發事件,最先捕獲到事件的為View所在的ViewGroup,然后才會到View自身~
下面我們按照日志的輸出,進入源碼~
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事件相關的代碼。
16行:進入ACTION_DOWN的處理
17-23行:將mMotionTarget置為null
26行:進行判斷:if(disallowIntercept || !onInterceptTouchEvent(ev))
兩種可能會進入IF代碼段
1、當前不允許攔截,即disallowIntercept =true,
2、當前允許攔截但是不攔截,即disallowIntercept =false,但是onInterceptTouchEvent(ev)返回false ;
注:disallowIntercept 可以通過viewGroup.requestDisallowInterceptTouchEvent(boolean);進行設置,后面會詳細說;而onInterceptTouchEvent(ev)可以進行復寫。
36-57行:開始遍歷所有的子View
41行:進行判斷當前的x,y坐標是否落在子View身上,如果在,47行,執行child.dispatchTouchEvent(ev),就進入了View的dispatchTouchEvent代碼中了,如果不了解請參考:Android View的事件分發機制,當child.dispatchTouchEvent(ev)返回true,則為mMotionTarget=child;然后return true;
ViewGroup的ACTION_DOWN分析結束,總結一下:
ViewGroup實現捕獲到DOWN事件,如果代碼中不做TOUCH事件攔截,則開始查找當前x,y是否在某個子View的區域內,如果在,則把事件分發下去。
按照日志,接下來到達ACTION_MOVE
2、ViewGroup -?dispatchTouchEvent - ACTION_MOVE
首先我們源碼進行刪減,只留下MOVE相關的代碼:
[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時賦值的mMotionTarget,付給target ;?
23行:if (!disallowIntercept && onInterceptTouchEvent(ev)) 當前允許攔截且攔截了,才進入IF體,當然了默認是不會攔截的~這里執行了onInterceptTouchEvent(ev)
28-30行:把坐標系統轉化為子View的坐標系統
32行:直接return?target.dispatchTouchEvent(ev);?
可以看到,正常流程下,ACTION_MOVE在檢測完是否攔截以后,直接調用了子View.dispatchTouchEvent,事件分發下去;
最后就是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行:判斷當前是否是ACTION_UP
21,28行:分別重置攔截標志位以及將DOWN賦值的mMotionTarget置為null,都UP了,當然置為null,下一次DOWN還會再賦值的~
最后,修改坐標系統,然后調用target.dispatchTouchEvent(ev);
正常情況下,即我們上例整個代碼的流程我們已經走完了:
1、ACTION_DOWN中,ViewGroup捕獲到事件,然后判斷是否攔截,如果沒有攔截,則找到包含當前x,y坐標的子View,賦值給mMotionTarget,然后調用 mMotionTarget.dispatchTouchEvent
2、ACTION_MOVE中,ViewGroup捕獲到事件,然后判斷是否攔截,如果沒有攔截,則直接調用mMotionTarget.dispatchTouchEvent(ev)
3、ACTION_UP中,ViewGroup捕獲到事件,然后判斷是否攔截,如果沒有攔截,則直接調用mMotionTarget.dispatchTouchEvent(ev)
當然了在分發之前都會修改下坐標系統,把當前的x,y分別減去child.left 和 child.top ,然后傳給child;
3、關于攔截
1、如何攔截
上面的總結都是基于:如果沒有攔截;那么如何攔截呢?
復寫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;??????}??
默認是不攔截的,即返回false;如果你需要攔截,只要return true就行了,這要該事件就不會往子View傳遞了,并且如果你在DOWN retrun true ,則DOWN,MOVE,UP子View都不會捕獲事件;如果你在MOVE return true , 則子View在MOVE和UP都不會捕獲事件。
原因很簡單,當onInterceptTouchEvent(ev) return true的時候,會把mMotionTarget 置為null ;?
2、如何不被攔截
如果ViewGroup的onInterceptTouchEvent(ev) 當ACTION_MOVE時return true ,即攔截了子View的MOVE以及UP事件;
此時子View希望依然能夠響應MOVE和UP時該咋辦呢?
Android給我們提供了一個方法:requestDisallowInterceptTouchEvent(boolean) 用于設置是否允許攔截,我們在子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的時候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;??????????}??
當我們把disallowIntercept設置為true時,!disallowIntercept直接為false,于是攔截的方法體就被跳過了~
注:如果ViewGroup在onInterceptTouchEvent(ev) ?ACTION_DOWN里面直接return true了,那么子View是木有辦法的捕獲事件的~~~
4、如果沒有找到合適的子View
我們的實例,直接點擊ViewGroup內的按鈕,當然直接很順利的走完整個流程;
但是有兩種特殊情況
1、ACTION_DOWN的時候,子View.dispatchTouchEvent(ev)返回的為false ;?
如果你仔細看了,你會注意到ViewGroup的dispatchTouchEvent(ev)的ACTION_DOWN代碼是這樣的
[java] view plaincopy
if?(child.dispatchTouchEvent(ev))??{????????????????????????????????????????????????????????????????mMotionTarget?=?child;????????????????????????????????return?true;????????????????????????????}??
只有在child.dispatchTouchEvent(ev)返回true了,才會認為找到了能夠處理當前事件的View,即mMotionTarget = child;
但是如果返回false,那么mMotionTarget 依然是null
mMotionTarget 為null會咋樣呢?
其實ViewGroup也是View的子類,如果沒有找到能夠處理該事件的子View,或者干脆就沒有子View;
那么,它作為一個View,就相當于View的事件轉發了~~直接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);?????????}??
我們沒有一個能夠處理該事件的目標元素,意味著我們需要自己處理~~~就相當于傳統的View~
2、那么什么時候子View.dispatchTouchEvent(ev)返回的為true
如果你仔細看了上篇博客,你會發現只要子View支持點擊或者長按事件一定返回true~~
源碼是這樣的:
[java] view plaincopy
???if?(((viewFlags?&?CLICKABLE)?==?CLICKABLE?||??????????????????(viewFlags?&?LONG_CLICKABLE)?==?LONG_CLICKABLE))?{??????????????????return?true?;???????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????}??
5、總結
關于代碼流程上面已經總結過了~
1、如果ViewGroup找到了能夠處理該事件的View,則直接交給子View處理,自己的onTouchEvent不會被觸發;
2、可以通過復寫onInterceptTouchEvent(ev)方法,攔截子View的事件(即return true),把事件交給自己處理,則會執行自己對應的onTouchEvent方法
3、子View可以通過調用getParent().requestDisallowInterceptTouchEvent(true); ?阻止ViewGroup對其MOVE或者UP事件進行攔截;
好了,那么實際應用中能解決哪些問題呢?
比如你需要寫一個類似slidingmenu的左側隱藏menu,主Activity上有個Button、ListView或者任何可以響應點擊的View,你在當前View上死命的滑動,菜單欄也出不來;因為MOVE事件被子View處理了~ 你需要這么做:在ViewGroup的dispatchTouchEvent中判斷用戶是不是想顯示菜單,如果是,則在onInterceptTouchEvent(ev)攔截子View的事件;自己進行處理,這樣自己的onTouchEvent就可以順利展現出菜單欄了~~
總結
以上是生活随笔為你收集整理的Android ViewGroup事件分发机制的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。