轉自:xiaanming的博客(http://blog.csdn.net/xiaanming/article/details/21696315)html
今天這篇文章主要分析的是Android的事件分發機制,採用例子加源碼的方式讓你們深入的理解Android事件分發的具體狀況,雖然網上不少Android的事件分發的文章,有些還寫的不錯,可是我仍是決定寫這篇文章,用我本身的思惟方式來幫助你們理解Android事件分發,Android事件分發到底有多重要呢?相信不少Android開發者都明白吧,這個我就不介紹了,我也寫了不少篇文章裏面涉及到Android的事件處理的問題,可能不理解Android事件分發的朋友會有點難理解吧,不過不要緊,相信看了這篇文章的你會對Android事件分發有進一步的理解。我這篇文章分析的源碼是Android 2.2的源碼, 也許你會說,幹嗎不去分析最新的源碼呢?我這裏要解釋一下,Android 2.2的源碼跟最新的源碼在功能效果方面是同樣的,只不過最新的源碼相對於Android 2.2來講更加健壯一些, Android 2.2的事件處理的代碼幾乎都寫在一個方法體裏面,而最新的源碼分了不少個方法寫,若是用最新的源碼調用方法會繞來繞去的,相信你看的也會暈,出於這個考慮,我就拿Android 2.2的源碼來給你們分析。java
ViewGroup的事件分發機制android
咱們用手指去觸摸Android手機屏幕,就會產生一個觸摸事件,可是這個觸摸事件在底層是怎麼分發的呢?這個我還真不知道,這裏涉及到操做硬件(手機屏幕)方面的知識,也就是Linux內核方面的知識,我也沒有了解過這方面的東西,因此咱們可能就往上層來分析分析,咱們知道Android中負責與用戶交互,與用戶操做緊密相關的四大組件之一是Activity, 因此咱們有理由相信Activity中存在分發事件的方法,這個方法就是dispatchTouchEvent(),咱們先看其源碼吧windows
1 public boolean dispatchTouchEvent(MotionEvent ev) { 2 3 //若是是按下狀態就調用onUserInteraction()方法,onUserInteraction()方法 4 //是個空的方法, 咱們直接跳過這裏看下面的實現 5 if (ev.getAction() == MotionEvent.ACTION_DOWN) { 6 onUserInteraction(); 7 } 8 9 if (getWindow().superDispatchTouchEvent(ev)) { 10 return true; 11 } 12 13 //getWindow().superDispatchTouchEvent(ev)返回false,這個事件就交給Activity 14 //來處理, Activity的onTouchEvent()方法直接返回了false 15 return onTouchEvent(ev); 16 }
這個方法中咱們仍是比較關心getWindow()的superDispatchTouchEvent()方法,getWindow()返回當前Activity的頂層窗口Window對象,咱們直接看Window API的superDispatchTouchEvent()方法ide
1 /** 2 * Used by custom windows, such as Dialog, to pass the touch screen event 3 * further down the view hierarchy. Application developers should 4 * not need to implement or call this. 5 * 6 */ 7 public abstract boolean superDispatchTouchEvent(MotionEvent event);
這個是個抽象方法,因此咱們直接找到其子類來看看superDispatchTouchEvent()方法的具體邏輯實現,Window的惟一子類是PhoneWindow,咱們就看看PhoneWindow的superDispatchTouchEvent()方法工具
1 public boolean superDispatchTouchEvent(KeyEvent event) { 2 return mDecor.superDispatcTouchEvent(event); 3 }
裏面直接調用DecorView類的superDispatchTouchEvent()方法,或許不少人不瞭解DecorView這個類,DecorView是PhoneWindow的一個final的內部類而且繼承FrameLayout的,也是Window界面的最頂層的View對象,這是什麼意思呢?彆着急,咱們接着往下看佈局
咱們先新建一個項目,取名AndroidTouchEvent,而後直接用模擬器運行項目, MainActivity的佈局文件爲post
<RelativeLayout 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" > <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerHorizontal="true" android:layout_centerVertical="true" android:text="@string/hello_world" /> </RelativeLayout>
利用hierarchyviewer工具來查看下MainActivity的View的層次結構,以下圖動畫
咱們看到最頂層就是PhoneWindow$DecorView,接着DecorView下面有一個LinearLayout, LinearLayout下面有兩個FrameLayoutthis
上面那個FrameLayout是用來顯示標題欄的,這個Demo中是一個TextView,固然咱們還能夠定製咱們的標題欄,利用getWindow().setFeatureInt(Window.FEATURE_CUSTOM_TITLE,R.layout.XXX); xxx就是咱們自定義標題欄的佈局XML文件
下面的FrameLayout是用來裝載ContentView的,也就是咱們在Activity中利用setContentView()方法設置的View,如今咱們知道了,原來咱們利用setContentView()設置Activity的View的外面還嵌套了這麼多的東西
咱們來理清下思路,Activity的最頂層窗體是PhoneWindow,而PhoneWindow的最頂層View是DecorView,接下來咱們就看DecorView類的superDispatchTouchEvent()方法
1 public boolean superDispatchTouchEvent(MotionEvent event) { 2 return super.dispatchTouchEvent(event); 3 }
在裏面調用了父類FrameLayout的dispatchTouchEvent()方法,而FrameLayout中並無dispatchTouchEvent()方法,因此咱們直接看ViewGroup的dispatchTouchEvent()方法
1 /** 2 * {@inheritDoc} 3 */ 4 @Override 5 public boolean dispatchTouchEvent(MotionEvent ev) { 6 final int action = ev.getAction(); 7 final float xf = ev.getX(); 8 final float yf = ev.getY(); 9 final float scrolledXFloat = xf + mScrollX; 10 final float scrolledYFloat = yf + mScrollY; 11 final Rect frame = mTempRect; 12 13 //這個值默認是false, 而後咱們能夠經過requestDisallowInterceptTouchEvent(boolean disallowIntercept)方法 14 //來改變disallowIntercept的值 15 boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0; 16 17 //這裏是ACTION_DOWN的處理邏輯 18 if (action == MotionEvent.ACTION_DOWN) { 19 //清除mMotionTarget, 每次ACTION_DOWN都很設置mMotionTarget爲null 20 if (mMotionTarget != null) { 21 mMotionTarget = null; 22 } 23 24 //disallowIntercept默認是false, 就看ViewGroup的onInterceptTouchEvent()方法 25 if (disallowIntercept || !onInterceptTouchEvent(ev)) { 26 ev.setAction(MotionEvent.ACTION_DOWN); 27 final int scrolledXInt = (int) scrolledXFloat; 28 final int scrolledYInt = (int) scrolledYFloat; 29 final View[] children = mChildren; 30 final int count = mChildrenCount; 31 //遍歷其子View 32 for (int i = count - 1; i >= 0; i--) { 33 final View child = children[i]; 34 35 //若是該子View是VISIBLE或者該子View正在執行動畫, 表示該View才 36 //能夠接受到Touch事件 37 if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE 38 || child.getAnimation() != null) { 39 //獲取子View的位置範圍 40 child.getHitRect(frame); 41 42 //如Touch到屏幕上的點在該子View上面 43 if (frame.contains(scrolledXInt, scrolledYInt)) { 44 // offset the event to the view's coordinate system 45 final float xc = scrolledXFloat - child.mLeft; 46 final float yc = scrolledYFloat - child.mTop; 47 ev.setLocation(xc, yc); 48 child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT; 49 50 //調用該子View的dispatchTouchEvent()方法 51 if (child.dispatchTouchEvent(ev)) { 52 // 若是child.dispatchTouchEvent(ev)返回true表示 53 //該事件被消費了,設置mMotionTarget爲該子View 54 mMotionTarget = child; 55 //直接返回true 56 return true; 57 } 58 // The event didn't get handled, try the next view. 59 // Don't reset the event's location, it's not 60 // necessary here. 61 } 62 } 63 } 64 } 65 } 66 67 //判斷是否爲ACTION_UP或者ACTION_CANCEL 68 boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) || 69 (action == MotionEvent.ACTION_CANCEL); 70 71 if (isUpOrCancel) { 72 //若是是ACTION_UP或者ACTION_CANCEL, 將disallowIntercept設置爲默認的false 73 //假如咱們調用了requestDisallowInterceptTouchEvent()方法來設置disallowIntercept爲true 74 //當咱們擡起手指或者取消Touch事件的時候要將disallowIntercept重置爲false 75 //因此說上面的disallowIntercept默認在咱們每次ACTION_DOWN的時候都是false 76 mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT; 77 } 78 79 // The event wasn't an ACTION_DOWN, dispatch it to our target if 80 // we have one. 81 final View target = mMotionTarget; 82 //mMotionTarget爲null意味着沒有找到消費Touch事件的View, 因此咱們須要調用ViewGroup父類的 83 //dispatchTouchEvent()方法,也就是View的dispatchTouchEvent()方法 84 if (target == null) { 85 // We don't have a target, this means we're handling the 86 // event as a regular view. 87 ev.setLocation(xf, yf); 88 if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) { 89 ev.setAction(MotionEvent.ACTION_CANCEL); 90 mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT; 91 } 92 return super.dispatchTouchEvent(ev); 93 } 94 95 //這個if裏面的代碼ACTION_DOWN不會執行,只有ACTION_MOVE 96 //ACTION_UP纔會走到這裏, 假如在ACTION_MOVE或者ACTION_UP攔截的 97 //Touch事件, 將ACTION_CANCEL派發給target,而後直接返回true 98 //表示消費了此Touch事件 99 if (!disallowIntercept && onInterceptTouchEvent(ev)) { 100 final float xc = scrolledXFloat - (float) target.mLeft; 101 final float yc = scrolledYFloat - (float) target.mTop; 102 mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT; 103 ev.setAction(MotionEvent.ACTION_CANCEL); 104 ev.setLocation(xc, yc); 105 106 if (!target.dispatchTouchEvent(ev)) { 107 } 108 // clear the target 109 mMotionTarget = null; 110 // Don't dispatch this event to our own view, because we already 111 // saw it when intercepting; we just want to give the following 112 // event to the normal onTouchEvent(). 113 return true; 114 } 115 116 if (isUpOrCancel) { 117 mMotionTarget = null; 118 } 119 120 // finally offset the event to the target's coordinate system and 121 // dispatch the event. 122 final float xc = scrolledXFloat - (float) target.mLeft; 123 final float yc = scrolledYFloat - (float) target.mTop; 124 ev.setLocation(xc, yc); 125 126 if ((target.mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) { 127 ev.setAction(MotionEvent.ACTION_CANCEL); 128 target.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT; 129 mMotionTarget = null; 130 } 131 132 //若是沒有攔截ACTION_MOVE, ACTION_DOWN的話,直接將Touch事件派發給target 133 return target.dispatchTouchEvent(ev); 134 }
這個方法相對來講仍是蠻長,不過全部的邏輯都寫在一塊兒,看起來比較方便,接下來咱們就具體來分析一下
咱們點擊屏幕上面的TextView來看看Touch是如何分發的,先看看ACTION_DOWN
在DecorView這一層會直接調用ViewGroup的dispatchTouchEvent(), 先看18行,每次ACTION_DOWN都會將mMotionTarget設置爲null, mMotionTarget是什麼?咱們先無論,繼續看代碼,走到25行, disallowIntercept默認爲false,咱們再看ViewGroup的onInterceptTouchEvent()方法
1 public boolean onInterceptTouchEvent(MotionEvent ev) { 2 return false; 3 }
直接返回false, 繼續往下看,循環遍歷DecorView裏面的Child,從上面的MainActivity的層次結構圖咱們能夠看出,DecorView裏面只有一個Child那就是LinearLayout, 第43行判斷Touch的位置在不在LinnearLayout上面,這是毫無疑問的,因此直接跳到51行, 調用LinearLayout的dispatchTouchEvent()方法,LinearLayout也沒有dispatchTouchEvent()這個方法,因此也是調用ViewGroup的dispatchTouchEvent()方法,因此這個方法卡在51行沒有繼續下去,而是去先執行LinearLayout的dispatchTouchEvent()
LinearLayout調用dispatchTouchEvent()的邏輯跟DecorView是同樣的,因此也是遍歷LinearLayout的兩個FrameLayout,判斷Touch的是哪一個FrameLayout,很明顯是下面那個,調用下面那個FrameLayout的dispatchTouchEvent(), 因此LinearLayout的dispatchTouchEvent()卡在51也沒繼續下去
繼續調用FrameLayout的dispatchTouchEvent()方法,和上面同樣的邏輯,下面的FrameLayout也只有一個Child,就是RelativeLayout,FrameLayout的dispatchTouchEvent()繼續卡在51行,先執行RelativeLayout的dispatchTouchEvent()方法
執行RelativeLayout的dispatchTouchEvent()方法邏輯仍是同樣的,循環遍歷 RelativeLayout裏面的孩子,裏面只有一個TextView, 因此這裏就調用TextView的dispatchTouchEvent(), TextView並無dispatchTouchEvent()這個方法,因而找TextView的父類View,在看View的dispatchTouchEvent()的方法以前,咱們先理清下上面這些ViewGroup執行dispatchTouchEvent()的思路,我畫了一張圖幫你們理清下(這裏沒有畫出onInterceptTouchEvent()方法)
上面的ViewGroup的Touch事件分發就告一段落先,由於這裏要調用TextView(也就是View)的dispatchTouchEvent()方法,因此咱們先分析View的dispatchTouchEvent()方法在將上面的繼續下去
View的Touch事件分發機制
咱們仍是先看View的dispatchTouchEvent()方法的源碼
1 public boolean dispatchTouchEvent(MotionEvent event) { 2 if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && 3 mOnTouchListener.onTouch(this, event)) { 4 return true; 5 } 6 return onTouchEvent(event); 7 }
在這個方法裏面,先進行了一個判斷
第一個條件mOnTouchListener就是咱們調用View的setTouchListener()方法設置的
第二個條件是判斷View是否爲enabled的, View通常都是enabled,除非你手動設置爲disabled
第三個條件就是OnTouchListener接口的onTouch()方法的返回值了,若是調用了setTouchListener()設置OnTouchListener,而且onTouch()方法返回true,View的dispatchTouchEvent()方法就直接返回true,不然就執行View的onTouchEvent() 並返回View的onTouchEvent()的值
如今你瞭解了View的onTouchEvent()方法和onTouch()的關係了吧,爲何Android提供了處理Touch事件onTouchEvent()方法還要增長一個OnTouchListener接口呢?我以爲OnTouchListener接口是對處理Touch事件的屏蔽和擴展做用吧,屏蔽做用我就不舉例介紹了,看上面的源碼就知道了,我就說下擴展吧,好比咱們要打印View的Touch的點的座標,咱們能夠自定義一個View以下
1 public class CustomView extends View { 2 3 public CustomView(Context context, AttributeSet attrs) { 4 super(context, attrs); 5 } 6 7 public CustomView(Context context, AttributeSet attrs, int defStyle) { 8 super(context, attrs, defStyle); 9 } 10 11 @Override 12 public boolean onTouchEvent(MotionEvent event) { 13 14 Log.i("tag", "X的座標 = " + event.getX() + " Y的座標 = " + event.getY()); 15 16 return super.onTouchEvent(event); 17 } 18 19 }
也能夠直接對View設置OnTouchListener接口,在return的時候調用下v.onTouchEvent()
1 view.setOnTouchListener(new OnTouchListener() { 2 3 @Override 4 public boolean onTouch(View v, MotionEvent event) { 5 6 Log.i("tag", "X的座標 = " + event.getX() + " Y的座標 = " + event.getY()); 7 8 return v.onTouchEvent(event); 9 } 10 });
這樣子也實現了咱們所須要的功能,因此我認爲OnTouchListener是對onTouchEvent()方法的一個屏蔽和擴展做用,假如你有不同的理解,你也能夠告訴我下,這裏就不糾結這個了。
咱們再看View的onTouchEvent()方法
1 public boolean onTouchEvent(MotionEvent event) { 2 final int viewFlags = mViewFlags; 3 4 if ((viewFlags & ENABLED_MASK) == DISABLED) { 5 return (((viewFlags & CLICKABLE) == CLICKABLE || 6 (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)); 7 } 8 9 //若是設置了Touch代理,就交給代理來處理,mTouchDelegate默認是null 10 if (mTouchDelegate != null) { 11 if (mTouchDelegate.onTouchEvent(event)) { 12 return true; 13 } 14 } 15 16 //若是View是clickable或者longClickable的onTouchEvent就返回true, 不然返回false 17 if (((viewFlags & CLICKABLE) == CLICKABLE || 18 (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) { 19 switch (event.getAction()) { 20 case MotionEvent.ACTION_UP: 21 boolean prepressed = (mPrivateFlags & PREPRESSED) != 0; 22 if ((mPrivateFlags & PRESSED) != 0 || prepressed) { 23 boolean focusTaken = false; 24 if (isFocusable() && isFocusableInTouchMode() && !isFocused()) { 25 focusTaken = requestFocus(); 26 } 27 28 if (!mHasPerformedLongPress) { 29 removeLongPressCallback(); 30 31 if (!focusTaken) { 32 if (mPerformClick == null) { 33 mPerformClick = new PerformClick(); 34 } 35 if (!post(mPerformClick)) { 36 performClick(); 37 } 38 } 39 } 40 41 if (mUnsetPressedState == null) { 42 mUnsetPressedState = new UnsetPressedState(); 43 } 44 45 if (prepressed) { 46 mPrivateFlags |= PRESSED; 47 refreshDrawableState(); 48 postDelayed(mUnsetPressedState, 49 ViewConfiguration.getPressedStateDuration()); 50 } else if (!post(mUnsetPressedState)) { 51 mUnsetPressedState.run(); 52 } 53 removeTapCallback(); 54 } 55 break; 56 57 case MotionEvent.ACTION_DOWN: 58 if (mPendingCheckForTap == null) { 59 mPendingCheckForTap = new CheckForTap(); 60 } 61 mPrivateFlags |= PREPRESSED; 62 mHasPerformedLongPress = false; 63 postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout()); 64 break; 65 66 case MotionEvent.ACTION_CANCEL: 67 mPrivateFlags &= ~PRESSED; 68 refreshDrawableState(); 69 removeTapCallback(); 70 break; 71 72 case MotionEvent.ACTION_MOVE: 73 final int x = (int) event.getX(); 74 final int y = (int) event.getY(); 75 76 //當手指在View上面滑動超過View的邊界, 77 int slop = mTouchSlop; 78 if ((x < 0 - slop) || (x >= getWidth() + slop) || 79 (y < 0 - slop) || (y >= getHeight() + slop)) { 80 // Outside button 81 removeTapCallback(); 82 if ((mPrivateFlags & PRESSED) != 0) { 83 removeLongPressCallback(); 84 85 mPrivateFlags &= ~PRESSED; 86 refreshDrawableState(); 87 } 88 } 89 break; 90 } 91 return true; 92 } 93 94 return false; 95 }
這個方法也是比較長的,咱們先看第4行,若是一個View是disabled, 而且該View是Clickable或者longClickable, onTouchEvent()就不執行下面的代碼邏輯直接返回true, 表示該View就一直消費Touch事件,若是一個enabled的View,而且是clickable或者longClickable的,onTouchEvent()會執行下面的代碼邏輯並返回true,綜上,一個clickable或者longclickable的View是一直消費Touch事件的,而通常的View既不是clickable也不是longclickable的(即不會消費Touch事件,只會執行ACTION_DOWN而不會執行ACTION_MOVE和ACTION_UP) Button是clickable的,能夠消費Touch事件,可是咱們能夠經過setClickable()和setLongClickable()來設置View是否爲clickable和longClickable。固然還能夠經過重寫View的onTouchEvent()方法來控制Touch事件的消費與否
咱們在看57行的ACTION_DOWN, 新建一個CheckForTap,咱們看看CheckForTap是什麼
1 private final class CheckForTap implements Runnable { 2 public void run() { 3 mPrivateFlags &= ~PREPRESSED; 4 mPrivateFlags |= PRESSED; 5 refreshDrawableState(); 6 if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) { 7 postCheckForLongClick(ViewConfiguration.getTapTimeout()); 8 } 9 } 10 }
原來是個Runnable對象,而後使用Handler的post方法延時ViewConfiguration.getTapTimeout()執行CheckForTap的run()方法,在run方法中先判斷view是否longClickable的,通常的View都是false, postCheckForLongClick(ViewConfiguration.getTapTimeout())這段代碼就是執行長按的邏輯的代碼,只有當咱們設置爲longClickble纔會去執行postCheckForLongClick(ViewConfiguration.getTapTimeout()),這裏我就不介紹了
因爲考慮到文章篇幅的問題,我就不繼續分析View的長按事件和點擊事件了,在這裏我直接得出結論吧
長按事件是在ACTION_DOWN中執行,點擊事件是在ACTION_UP中執行,要想執行長按事件,這個View必須是longclickable的, 也許你會納悶,通常的View不是longClickable爲何也會執行長按事件呢?咱們要執行長按事件必需要調用setOnLongClickListener()設置OnLongClickListener接口,咱們看看這個方法的源碼
1 public void setOnLongClickListener(OnLongClickListener l) { 2 if (!isLongClickable()) { 3 setLongClickable(true); 4 } 5 mOnLongClickListener = l; 6 }
看到沒有,若是這個View不是longClickable的,咱們就調用setLongClickable(true)方法設置爲longClickable的,因此纔會去執行長按方法onLongClick();
要想執行點擊事件,這個View就必需要消費ACTION_DOWN和ACTION_MOVE事件,而且沒有設置OnLongClickListener的狀況下,若是設置了OnLongClickListener的狀況下,須要onLongClick()返回false才能執行到onClick()方法,也許你又會納悶,通常的View默認是不消費touch事件的,這不是和你上面說的相違背嘛,咱們要向執行點擊事件必需要調用setOnClickListener()來設置OnClickListener接口,咱們看看這個方法的源碼就知道了
1 public void setOnClickListener(OnClickListener l) { 2 if (!isClickable()) { 3 setClickable(true); 4 } 5 mOnClickListener = l; 6 }
因此說一個enable的而且是clickable的View是一直消費touch事件的,因此纔會執行到onClick()方法
對於View的Touch事件的分發機制算是告一段落了,從上面咱們能夠得出TextView的dispatchTouchEvent()的返回false的,即不消費Touch事件。咱們就要往上看RelativeLayout的dispatchTouchEvent()方法的51行,因爲TextView.dispatchTouchEvent()爲false, 致使mMotionTarget沒有被賦值,仍是null, 繼續往下走執行RelativeLayout的dispatchTouchEvent()方法, 來到第84行, 判斷target是否爲null,這個target就是mMotionTarget,知足條件,執行92行的 super.dispatchTouchEvent(ev)代碼並返回, 這裏調用的是RelativeLayout父類View的dispatchTouchEvent()方法,因爲RelativeLayout沒有設置onTouchListener, 因此這裏直接調用RelativeLayout(其實就是View, 由於RelativeLayout沒有重寫onTouchEvent())的onTouchEvent()方法 因爲RelativeLayout既不是clickable的也是longClickable的,因此其onTouchEvent()方法false, RelativeLayout的dispatchTouchEvent()也是返回false,這裏就執行完了RelativeLayout的dispatchTouchEvent()方法
繼續執行FrameLayout的dispatchTouchEvent()的第51行,因爲RelativeLayout.dispatchTouchEvent()返回的是false, 跟上面的邏輯是同樣的, 也是執行到92行的super.dispatchTouchEvent(ev)代碼並返回,而後執行FrameLayout的onTouchEvent()方法,而FrameLayout的onTouchEvent()也是返回false,因此FrameLayout的dispatchTouchEvent()方法返回false,執行完畢FrameLayout的dispatchTouchEvent()方法
在上面的我就不分析了,你們自行分析一下,跟上面的邏輯是同樣的,我直接畫了個圖來幫你們理解下(這裏沒有畫出onInterceptTouchEvent()方法)
因此咱們點擊屏幕上面的TextView的事件分發流程是上圖那個樣子的,表示Activity的View都不消費ACTION_DOWN事件,因此就不能在觸發ACTION_MOVE, ACTION_UP等事件了,具體是爲何?我還不太清楚,畢竟從Activity到TextView這一層是分析不出來的,估計是在底層實現的。
但若是將TextView換成Button,流程是否是仍是這個樣子呢?答案不是,咱們來分析分析一下,若是是Button , Button是一個clickable的View,onTouchEvent()返回true, 表示他一直消費Touch事件,因此Button的dispatchTouchEvent()方法返回true, 回到RelativeLayout的dispatchTouchEvent()方法的51行,知足條件,進入到if方法體,設置mMotionTarget爲Button,而後直接返回true, RelativeLayout的dispatchTouchEvent()方法執行完畢, 不會調用到RelativeLayout的onTouchEvent()方法
而後到FrameLayout的dispatchTouchEvent()方法的51行,因爲RelativeLayout.dispatchTouchEvent()返回true, 知足條件,進入if方法體,設置mMotionTarget爲RelativeLayout,注意下,這裏的mMotionTarget跟RelativeLayout的dispatchTouchEvent()方法的mMotionTarget不是同一個哦,由於他們是不一樣的方法中的,而後返回true
同理FrameLayout的dispatchTouchEvent()也是返回true, DecorView的dispatchTouchEvent()方法也返回true, 仍是畫一個流程圖(這裏沒有畫出onInterceptTouchEvent()方法)給你們理清下
從上面的流程圖得出一個結論,Touch事件是從頂層的View一直往下分發到手指按下的最裏面的View,若是這個View的onTouchEvent()返回false,即不消費Touch事件,這個Touch事件就會向上找父佈局調用其父佈局的onTouchEvent()處理,若是這個View返回true,表示消費了Touch事件,就不調用父佈局的onTouchEvent()
接下來咱們用一個自定義的ViewGroup來替換RelativeLayout,自定義ViewGroup代碼以下
1 package com.example.androidtouchevent; 2 3 import android.content.Context; 4 import android.util.AttributeSet; 5 import android.view.MotionEvent; 6 import android.widget.RelativeLayout; 7 8 public class CustomLayout extends RelativeLayout { 9 10 public CustomLayout(Context context, AttributeSet attrs) { 11 super(context, attrs, 0); 12 } 13 14 public CustomLayout(Context context, AttributeSet attrs, int defStyle) { 15 super(context, attrs, defStyle); 16 } 17 18 @Override 19 public boolean onTouchEvent(MotionEvent event) { 20 return super.onTouchEvent(event); 21 } 22 23 @Override 24 public boolean onInterceptTouchEvent(MotionEvent ev) { 25 return true; 26 } 27 28 29 }
咱們就重寫了onInterceptTouchEvent(),返回true, RelativeLayout默認是返回false, 而後再CustomLayout佈局中加一個Button ,以下圖
咱們此次不從DecorView的dispatchTouchEvent()分析了,直接從CustomLayout的dispatchTouchEvent()分析
咱們先看ACTION_DOWN 來到25行,因爲咱們重寫了onInterceptTouchEvent()返回true, 因此不走這個if裏面,直接往下看代碼,來到84行, target爲null,因此進入if方法裏面,直接調用super.dispatchTouchEvent()方法, 也就是View的dispatchTouchEvent()方法,而在View的dispatchTouchEvent()方法中是直接調用View的onTouchEvent()方法,可是CustomLayout重寫了onTouchEvent(),因此這裏仍是調用CustomLayout的onTouchEvent(), 這個方法返回false, 不消費Touch事件,因此不會在觸發ACTION_MOVE,ACTION_UP等事件了,這裏我再畫一個流程圖吧(含有onInterceptTouchEvent()方法的)
好了,就分析到這裏吧,差很少分析完了,還有一種狀況沒有分析到,例如我將CustomLayout的代碼改爲下面的情形,Touch事件又是怎麼分發的呢?我這裏就不帶你們分析了
1 package com.example.androidtouchevent; 2 3 import android.content.Context; 4 import android.util.AttributeSet; 5 import android.view.MotionEvent; 6 import android.widget.RelativeLayout; 7 8 public class CustomLayout extends RelativeLayout { 9 10 public CustomLayout(Context context, AttributeSet attrs) { 11 super(context, attrs, 0); 12 } 13 14 public CustomLayout(Context context, AttributeSet attrs, int defStyle) { 15 super(context, attrs, defStyle); 16 } 17 18 @Override 19 public boolean onTouchEvent(MotionEvent event) { 20 return super.onTouchEvent(event); 21 } 22 23 @Override 24 public boolean onInterceptTouchEvent(MotionEvent ev) { 25 if(ev.getAction() == MotionEvent.ACTION_MOVE){ 26 return true; 27 } 28 return super.onInterceptTouchEvent(ev); 29 } 30 31 32 }
這篇文章的篇幅有點長,若是你想了解Touch事件的分發機制,你必定要認真看完,下面來總結一下吧
1.Activity的最頂層Window是PhoneWindow,PhoneWindow的最頂層View是DecorView
2.一個clickable或者longClickable的View會永遠消費Touch事件,無論他是enabled仍是disabled的
3.View的長按事件是在ACTION_DOWN中執行,要想執行長按事件該View必須是longClickable的,而且不能產生ACTION_MOVE
4.View的點擊事件是在ACTION_UP中執行,想要執行點擊事件的前提是消費了ACTION_DOWN和ACTION_MOVE,而且沒有設置OnLongClickListener的狀況下,如設置了OnLongClickListener的狀況,則必須使onLongClick()返回false
5.若是View設置了onTouchListener了,而且onTouch()方法返回true,則不執行View的onTouchEvent()方法,也表示View消費了Touch事件,返回false則繼續執行onTouchEvent()
6.Touch事件是從最頂層的View一直分發到手指touch的最裏層的View,若是最裏層View消費了ACTION_DOWN事件(設置onTouchListener,而且onTouch()返回true 或者onTouchEvent()方法返回true)纔會觸發ACTION_MOVE,ACTION_UP的發生,若是某個ViewGroup攔截了Touch事件,則Touch事件交給ViewGroup處理
7.Touch事件的分發過程當中,若是消費了ACTION_DOWN,而在分發ACTION_MOVE的時候,某個ViewGroup攔截了Touch事件,就像上面那個自定義CustomLayout,則會將ACTION_CANCEL分發給該ViewGroup下面的Touch到的View,而後將Touch事件交給ViewGroup處理,並返回true