您好,登錄后才能下訂單哦!
今天這篇文章主要分析的是Android的事件分發(fā)機(jī)制,采用例子加源碼的方式讓大家深刻的理解Android事件分發(fā)的具體情況,雖然網(wǎng)上很多Android的事件分發(fā)的文章,有些還寫的不錯,但是我還是決定寫這篇文章,用我自己的思維方式來幫助大家理解Android事件分發(fā),Android事件分發(fā)到底有多重要呢?相信很多Android開發(fā)者都明白吧,這個我就不介紹了,我也寫了很多篇文章里面涉及到Android的事件處理的問題,可能不理解Android事件分發(fā)的朋友會有點難理解吧,不過沒關(guān)系,相信看了這篇文章的你會對Android事件分發(fā)有進(jìn)一步的理解。我這篇文章分析的源碼是Android 2.2的源碼, 也許你會說,干嘛不去分析最新的源碼呢?我這里要解釋一下,Android 2.2的源碼跟最新的源碼在功能效果方面是一樣的,只不過最新的源碼相對于Android 2.2來說更加健壯一些, Android 2.2的事件處理的代碼幾乎都寫在一個方法體里面,而最新的源碼分了很多個方法寫,如果用最新的源碼調(diào)用方法會繞來繞去的,相信你看的也會暈,出于這個考慮,我就拿Android 2.2的源碼來給大家分析。
ViewGroup的事件分發(fā)機(jī)制
我們用手指去觸摸Android手機(jī)屏幕,就會產(chǎn)生一個觸摸事件,但是這個觸摸事件在底層是怎么分發(fā)的呢?這個我還真不知道,這里涉及到操作硬件(手機(jī)屏幕)方面的知識,也就是Linux內(nèi)核方面的知識,我也沒有了解過這方面的東西,所以我們可能就往上層來分析分析,我們知道Android中負(fù)責(zé)與用戶交互,與用戶操作緊密相關(guān)的四大組件之一是Activity, 所以我們有理由相信Activity中存在分發(fā)事件的方法,這個方法就是dispatchTouchEvent(),我們先看其源碼吧
public boolean dispatchTouchEvent(MotionEvent ev) { //如果是按下狀態(tài)就調(diào)用onUserInteraction()方法,onUserInteraction()方法 //是個空的方法, 我們直接跳過這里看下面的實現(xiàn) if (ev.getAction() == MotionEvent.ACTION_DOWN) { onUserInteraction(); } if (getWindow().superDispatchTouchEvent(ev)) { return true; } //getWindow().superDispatchTouchEvent(ev)返回false,這個事件就交給Activity //來處理, Activity的onTouchEvent()方法直接返回了false return onTouchEvent(ev); }
這個方法中我們還是比較關(guān)心getWindow()的superDispatchTouchEvent()方法,getWindow()返回當(dāng)前Activity的頂層窗口Window對象,我們直接看Window API的superDispatchTouchEvent()方法
/** * Used by custom windows, such as Dialog, to pass the touch screen event * further down the view hierarchy. Application developers should * not need to implement or call this. * */ public abstract boolean superDispatchTouchEvent(MotionEvent event);
這個是個抽象方法,所以我們直接找到其子類來看看superDispatchTouchEvent()方法的具體邏輯實現(xiàn),Window的唯一子類是PhoneWindow,我們就看看PhoneWindow的superDispattEvent()
public boolean superDispatchTouchEvent(KeyEvent event) { return mDecor.superDispatchTouchEvent(event); }
里面直接調(diào)用DecorView類的superDispatchTouchEvent方法,或許很多人不了解DecorView這個類,DecorView是PhoneWindow的一個final的內(nèi)部類并且繼承FrameLayout的,也是Window界面的最頂層的View對象,這是什么意思呢?別著急,我們接著往下看
我們先新建一個項目,取名AndroidTouchEvent,然后直接用模擬器運行項目, MainActivity的布局文件為
<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的層次結(jié)構(gòu),如下圖
我們看到最頂層就是PhoneWindow$DecorView,接著DecorView下面有一個LinearLayout, LinearLayout下面有兩個FrameLayout
上面那個FrameLayout是用來顯示標(biāo)題欄的,這個Demo中是一個TextView,當(dāng)然我們還可以定制我們的標(biāo)題欄,利用getWindow().setFeatureInt(Window.FEATURE_CUSTOM_TITLE,R.layout.XXX); xxx就是我們自定義標(biāo)題欄的布局XML文件
下面的FrameLayout是用來裝載ContentView的,也就是我們在Activity中利用setContentView()方法設(shè)置的View,現(xiàn)在我們知道了,原來我們利用setContentView()設(shè)置Activity的View的外面還嵌套了這么多的東西
我們來理清下思路,Activity的最頂層窗體是PhoneWindow,而PhoneWindow的最頂層View是DecorView,接下來我們就看DecorView類的superDispatchTouchEvent()方法
public boolean superDispatchTouchEvent(MotionEvent event) { return super.dispatchTouchEvent(event); }
在里面調(diào)用了父類FrameLayout的dispatchTouchEvent()方法,而FrameLayout中并沒有dispatchTouchEvent()方法,所以我們直接看ViewGroup的dispatchTouchEvent()方法
/** * {@inheritDoc} */ @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; //這個值默認(rèn)是false, 然后我們可以通過requestDisallowInterceptTouchEvent(boolean disallowIntercept)方法 //來改變disallowIntercept的值 boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0; //這里是ACTION_DOWN的處理邏輯 if (action == MotionEvent.ACTION_DOWN) { //清除mMotionTarget, 每次ACTION_DOWN都很設(shè)置mMotionTarget為null if (mMotionTarget != null) { mMotionTarget = null; } //disallowIntercept默認(rèn)是false, 就看ViewGroup的onInterceptTouchEvent()方法 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; //遍歷其子View for (int i = count - 1; i >= 0; i--) { final View child = children[i]; //如果該子View是VISIBLE或者該子View正在執(zhí)行動畫, 表示該View才 //可以接受到Touch事件 if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) { //獲取子View的位置范圍 child.getHitRect(frame); //如Touch到屏幕上的點在該子View上面 if (frame.contains(scrolledXInt, scrolledYInt)) { // offset the event to the view's coordinate system final float xc = scrolledXFloat - child.mLeft; final float yc = scrolledYFloat - child.mTop; ev.setLocation(xc, yc); child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT; //調(diào)用該子View的dispatchTouchEvent()方法 if (child.dispatchTouchEvent(ev)) { // 如果child.dispatchTouchEvent(ev)返回true表示 //該事件被消費了,設(shè)置mMotionTarget為該子View mMotionTarget = child; //直接返回true return true; } // The event didn't get handled, try the next view. // Don't reset the event's location, it's not // necessary here. } } } } } //判斷是否為ACTION_UP或者ACTION_CANCEL boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) || (action == MotionEvent.ACTION_CANCEL); if (isUpOrCancel) { //如果是ACTION_UP或者ACTION_CANCEL, 將disallowIntercept設(shè)置為默認(rèn)的false //假如我們調(diào)用了requestDisallowInterceptTouchEvent()方法來設(shè)置disallowIntercept為true //當(dāng)我們抬起手指或者取消Touch事件的時候要將disallowIntercept重置為false //所以說上面的disallowIntercept默認(rèn)在我們每次ACTION_DOWN的時候都是false mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT; } // The event wasn't an ACTION_DOWN, dispatch it to our target if // we have one. final View target = mMotionTarget; //mMotionTarget為null意味著沒有找到消費Touch事件的View, 所以我們需要調(diào)用ViewGroup父類的 //dispatchTouchEvent()方法,也就是View的dispatchTouchEvent()方法 if (target == null) { // We don't have a target, this means we're handling the // event as a regular view. 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里面的代碼ACTION_DOWN不會執(zhí)行,只有ACTION_MOVE //ACTION_UP才會走到這里, 假如在ACTION_MOVE或者ACTION_UP攔截的 //Touch事件, 將ACTION_CANCEL派發(fā)給target,然后直接返回true //表示消費了此Touch事件 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)) { } // clear the target mMotionTarget = null; // Don't dispatch this event to our own view, because we already // saw it when intercepting; we just want to give the following // event to the normal onTouchEvent(). return true; } if (isUpOrCancel) { mMotionTarget = null; } // finally offset the event to the target's coordinate system and // dispatch the event. 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; } //如果沒有攔截ACTION_MOVE, ACTION_DOWN的話,直接將Touch事件派發(fā)給target return target.dispatchTouchEvent(ev); }
這個方法相對來說還是蠻長,不過所有的邏輯都寫在一起,看起來比較方便,接下來我們就具體來分析一下
我們點擊屏幕上面的TextView來看看Touch是如何分發(fā)的,先看看ACTION_DOWN
在DecorView這一層會直接調(diào)用ViewGroup的dispatchTouchEvent(), 先看18行,每次ACTION_DOWN都會將mMotionTarget設(shè)置為null, mMotionTarget是什么?我們先不管,繼續(xù)看代碼,走到25行, disallowIntercept默認(rèn)為false,我們再看ViewGroup的onInterceptTouchEvent()方法
public boolean onInterceptTouchEvent(MotionEvent ev) { return false; }
直接返回false, 繼續(xù)往下看,循環(huán)遍歷DecorView里面的Child,從上面的MainActivity的層次結(jié)構(gòu)圖我們可以看出,DecorView里面只有一個Child那就是LinearLayout, 第43行判斷Touch的位置在不在LinnearLayout上面,這是毫無疑問的,所以直接跳到51行, 調(diào)用LinearLayout的dispatchTouchEvent()方法,LinearLayout也沒有dispatchTouchEvent()這個方法,所以也是調(diào)用ViewGroup的dispatchTouchEvent()方法,所以這個方法卡在51行沒有繼續(xù)下去,而是去先執(zhí)行LinearLayout的dispatchTouchEvent()
LinearLayout調(diào)用dispatchTouchEvent()的邏輯跟DecorView是一樣的,所以也是遍歷LinearLayout的兩個FrameLayout,判斷Touch的是哪個FrameLayout,很明顯是下面那個,調(diào)用下面那個FrameLayout的dispatchTouchEvent(), 所以LinearLayout的dispatchTouchEvent()卡在51也沒繼續(xù)下去
繼續(xù)調(diào)用FrameLayout的dispatchTouchEvent()方法,和上面一樣的邏輯,下面的FrameLayout也只有一個Child,就是RelativeLayout,F(xiàn)rameLayout的dispatchTouchEvent()繼續(xù)卡在51行,先執(zhí)行RelativeLayout的dispatchTouchEvent()方法
執(zhí)行RelativeLayout的dispatchTouchEvent()方法邏輯還是一樣的,循環(huán)遍歷 RelativeLayout里面的孩子,里面只有一個TextView, 所以這里就調(diào)用TextView的dispatchTouchEvent(), TextView并沒有dispatchTouchEvent()這個方法,于是找TextView的父類View,在看View的dispatchTouchEvent()的方法之前,我們先理清下上面這些ViewGroup執(zhí)行dispatchTouchEvent()的思路,我畫了一張圖幫大家理清下(這里沒有畫出onInterceptTouchEvent()方法)
上面的ViewGroup的Touch事件分發(fā)就告一段落先,因為這里要調(diào)用TextView(也就是View)的dispatchTouchEvent()方法,所以我們先分析View的dispatchTouchEvent()方法在將上面的繼續(xù)下去
View的Touch事件分發(fā)機(jī)制
我們還是先看View的dispatchTouchEvent()方法的源碼
public boolean dispatchTouchEvent(MotionEvent event) { if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && mOnTouchListener.onTouch(this, event)) { return true; } return onTouchEvent(event); }
在這個方法里面,先進(jìn)行了一個判斷
第一個條件mOnTouchListener就是我們調(diào)用View的setTouchListener()方法設(shè)置的
第二個條件是判斷View是否為enabled的, View一般都是enabled,除非你手動設(shè)置為disabled
第三個條件就是OnTouchListener接口的onTouch()方法的返回值了,如果調(diào)用了setTouchListener()設(shè)置OnTouchListener,并且onTouch()方法返回true,View的dispatchTouchEvent()方法就直接返回true,否則就執(zhí)行View的onTouchEvent() 并返回View的onTouchEvent()的值
現(xiàn)在你了解了View的onTouchEvent()方法和onTouch()的關(guān)系了吧,為什么Android提供了處理Touch事件onTouchEvent()方法還要增加一個OnTouchListener接口呢?我覺得OnTouchListener接口是對處理Touch事件的屏蔽和擴(kuò)展作用吧,屏蔽作用我就不舉例介紹了,看上面的源碼就知道了,我就說下擴(kuò)展吧,比如我們要打印View的Touch的點的坐標(biāo),我們可以自定義一個View如下
public class CustomView extends View { public CustomView(Context context, AttributeSet attrs) { super(context, attrs); } public CustomView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } @Override public boolean onTouchEvent(MotionEvent event) { Log.i("tag", "X的坐標(biāo) = " + event.getX() + " Y的坐標(biāo) = " + event.getY()); return super.onTouchEvent(event); } }
也可以直接對View設(shè)置OnTouchListener接口,在return的時候調(diào)用下v.onTouchEvent()
view.setOnTouchListener(new OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { Log.i("tag", "X的坐標(biāo) = " + event.getX() + " Y的坐標(biāo) = " + event.getY()); return v.onTouchEvent(event); } });
這樣子也實現(xiàn)了我們所需要的功能,所以我認(rèn)為OnTouchListener是對onTouchEvent()方法的一個屏蔽和擴(kuò)展作用,假如你有不一樣的理解,你也可以告訴我下,這里就不糾結(jié)這個了。
我們再看View的onTouchEvent()方法
public boolean onTouchEvent(MotionEvent event) { final int viewFlags = mViewFlags; if ((viewFlags & ENABLED_MASK) == DISABLED) { return (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)); } //如果設(shè)置了Touch代理,就交給代理來處理,mTouchDelegate默認(rèn)是null if (mTouchDelegate != null) { if (mTouchDelegate.onTouchEvent(event)) { return true; } } //如果View是clickable或者longClickable的onTouchEvent就返回true, 否則返回false if (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) { switch (event.getAction()) { case MotionEvent.ACTION_UP: boolean prepressed = (mPrivateFlags & PREPRESSED) != 0; if ((mPrivateFlags & PRESSED) != 0 || prepressed) { boolean focusTaken = false; if (isFocusable() && isFocusableInTouchMode() && !isFocused()) { focusTaken = requestFocus(); } if (!mHasPerformedLongPress) { removeLongPressCallback(); if (!focusTaken) { if (mPerformClick == null) { mPerformClick = new PerformClick(); } if (!post(mPerformClick)) { performClick(); } } } if (mUnsetPressedState == null) { mUnsetPressedState = new UnsetPressedState(); } if (prepressed) { mPrivateFlags |= PRESSED; refreshDrawableState(); postDelayed(mUnsetPressedState, ViewConfiguration.getPressedStateDuration()); } else if (!post(mUnsetPressedState)) { mUnsetPressedState.run(); } removeTapCallback(); } break; case MotionEvent.ACTION_DOWN: if (mPendingCheckForTap == null) { mPendingCheckForTap = new CheckForTap(); } mPrivateFlags |= PREPRESSED; mHasPerformedLongPress = false; postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout()); break; case MotionEvent.ACTION_CANCEL: mPrivateFlags &= ~PRESSED; refreshDrawableState(); removeTapCallback(); break; case MotionEvent.ACTION_MOVE: final int x = (int) event.getX(); final int y = (int) event.getY(); //當(dāng)手指在View上面滑動超過View的邊界, int slop = mTouchSlop; if ((x < 0 - slop) || (x >= getWidth() + slop) || (y < 0 - slop) || (y >= getHeight() + slop)) { // Outside button removeTapCallback(); if ((mPrivateFlags & PRESSED) != 0) { removeLongPressCallback(); mPrivateFlags &= ~PRESSED; refreshDrawableState(); } } break; } return true; } return false; }
這個方法也是比較長的,我們先看第4行,如果一個View是disabled, 并且該View是Clickable或者longClickable, onTouchEvent()就不執(zhí)行下面的代碼邏輯直接返回true, 表示該View就一直消費Touch事件,如果一個enabled的View,并且是clickable或者longClickable的,onTouchEvent()會執(zhí)行下面的代碼邏輯并返回true,綜上,一個clickable或者longclickable的View是一直消費Touch事件的,而一般的View既不是clickable也不是longclickable的(即不會消費Touch事件,只會執(zhí)行ACTION_DOWN而不會執(zhí)行ACTION_MOVE和ACTION_UP) Button是clickable的,可以消費Touch事件,但是我們可以通過setClickable()和setLongClickable()來設(shè)置View是否為clickable和longClickable。當(dāng)然還可以通過重寫View的onTouchEvent()方法來控制Touch事件的消費與否
我們在看57行的ACTION_DOWN, 新建一個CheckForTap,我們看看CheckForTap是什么
private final class CheckForTap implements Runnable { public void run() { mPrivateFlags &= ~PREPRESSED; mPrivateFlags |= PRESSED; refreshDrawableState(); if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) { postCheckForLongClick(ViewConfiguration.getTapTimeout()); } } }
原來是個Runnable對象,然后使用Handler的post方法延時ViewConfiguration.getTapTimeout()執(zhí)行CheckForTap的run()方法,在run方法中先判斷view是否longClickable的,一般的View都是false, postCheckForLongClick(ViewConfiguration.getTapTimeout())這段代碼就是執(zhí)行長按的邏輯的代碼,只有當(dāng)我們設(shè)置為longClickble才會去執(zhí)行postCheckForLongClick(ViewConfiguration.getTapTimeout()),這里我就不介紹了
由于考慮到文章篇幅的問題,我就不繼續(xù)分析View的長按事件和點擊事件了,在這里我直接得出結(jié)論吧
長按事件是在ACTION_DOWN中執(zhí)行,點擊事件是在ACTION_UP中執(zhí)行,要想執(zhí)行長按事件,這個View必須是longclickable的, 也許你會納悶,一般的View不是longClickable為什么也會執(zhí)行長按事件呢?我們要執(zhí)行長按事件必須要調(diào)用setOnLongClickListener()設(shè)置OnLongClickListener接口,我們看看這個方法的源碼
public void setOnLongClickListener(OnLongClickListener l) { if (!isLongClickable()) { setLongClickable(true); } mOnLongClickListener = l; }
看到?jīng)]有,如果這個View不是longClickable的,我們就調(diào)用setLongClickable(true)方法設(shè)置為longClickable的,所以才會去執(zhí)行長按方法onLongClick();
要想執(zhí)行點擊事件,這個View就必須要消費ACTION_DOWN和ACTION_MOVE事件,并且沒有設(shè)置OnLongClickListener的情況下,如果設(shè)置了OnLongClickListener的情況下,需要onLongClick()返回false才能執(zhí)行到onClick()方法,也許你又會納悶,一般的View默認(rèn)是不消費touch事件的,這不是和你上面說的相違背嘛,我們要向執(zhí)行點擊事件必須要調(diào)用setOnClickListener()來設(shè)置OnClickListener接口,我們看看這個方法的源碼就知道了
public void setOnClickListener(OnClickListener l) { if (!isClickable()) { setClickable(true); } mOnClickListener = l; }
所以說一個enable的并且是clickable的View是一直消費touch事件的,所以才會執(zhí)行到onClick()方法
對于View的Touch事件的分發(fā)機(jī)制算是告一段落了,從上面我們可以得出TextView的dispatchTouchEvent()的返回false的,即不消費Touch事件。我們就要往上看RelativeLayout的dispatchTouchEvent()方法的51行,由于TextView.dispatchTouchEvent()為false, 導(dǎo)致mMotionTarget沒有被賦值,還是null, 繼續(xù)往下走執(zhí)行RelativeLayout的dispatchTouchEvent()方法, 來到第84行, 判斷target是否為null,這個target就是mMotionTarget,滿足條件,執(zhí)行92行的 super.dispatchTouchEvent(ev)代碼并返回, 這里調(diào)用的是RelativeLayout父類View的dispatchTouchEvent()方法,由于RelativeLayout沒有設(shè)置onTouchListener, 所以這里直接調(diào)用RelativeLayout(其實就是View, 因為RelativeLayout沒有重寫onTouchEvent())的onTouchEvent()方法 由于RelativeLayout既不是clickable的也是longClickable的,所以其onTouchEvent()方法false, RelativeLayout的dispatchTouchEvent()也是返回false,這里就執(zhí)行完了RelativeLayout的dispatchTouchEvent()方法
繼續(xù)執(zhí)行FrameLayout的dispatchTouchEvent()的第51行,由于RelativeLayout.dispatchTouchEvent()返回的是false, 跟上面的邏輯是一樣的, 也是執(zhí)行到92行的super.dispatchTouchEvent(ev)代碼并返回,然后執(zhí)行FrameLayout的onTouchEvent()方法,而FrameLayout的onTouchEvent()也是返回false,所以FrameLayout的dispatchTouchEvent()方法返回false,執(zhí)行完畢FrameLayout的dispatchTouchEvent()方法
在上面的我就不分析了,大家自行分析一下,跟上面的邏輯是一樣的,我直接畫了個圖來幫大家理解下(這里沒有畫出onInterceptTouchEvent()方法)
所以我們點擊屏幕上面的TextView的事件分發(fā)流程是上圖那個樣子的,表示Activity的View都不消費ACTION_DOWN事件,所以就不能在觸發(fā)ACTION_MOVE, ACTION_UP等事件了,具體是為什么?我還不太清楚,畢竟從Activity到TextView這一層是分析不出來的,估計是在底層實現(xiàn)的。
但如果將TextView換成Button,流程是不是還是這個樣子呢?答案不是,我們來分析分析一下,如果是Button , Button是一個clickable的View,onTouchEvent()返回true, 表示他一直消費Touch事件,所以Button的dispatchTouchEvent()方法返回true, 回到RelativeLayout的dispatchTouchEvent()方法的51行,滿足條件,進(jìn)入到if方法體,設(shè)置mMotionTarget為Button,然后直接返回true, RelativeLayout的dispatchTouchEvent()方法執(zhí)行完畢, 不會調(diào)用到RelativeLayout的onTouchEvent()方法
然后到FrameLayout的dispatchTouchEvent()方法的51行,由于RelativeLayout.dispatchTouchEvent()返回true, 滿足條件,進(jìn)入if方法體,設(shè)置mMotionTarget為RelativeLayout,注意下,這里的mMotionTarget跟RelativeLayout的dispatchTouchEvent()方法的mMotionTarget不是同一個哦,因為他們是不同的方法中的,然后返回true
同理FrameLayout的dispatchTouchEvent()也是返回true, DecorView的dispatchTouchEvent()方法也返回true, 還是畫一個流程圖(這里沒有畫出onInterceptTouchEvent()方法)給大家理清下
從上面的流程圖得出一個結(jié)論,Touch事件是從頂層的View一直往下分發(fā)到手指按下的最里面的View,如果這個View的onTouchEvent()返回false,即不消費Touch事件,這個Touch事件就會向上找父布局調(diào)用其父布局的onTouchEvent()處理,如果這個View返回true,表示消費了Touch事件,就不調(diào)用父布局的onTouchEvent()
接下來我們用一個自定義的ViewGroup來替換RelativeLayout,自定義ViewGroup代碼如下
package com.example.androidtouchevent; import android.content.Context; import android.util.AttributeSet; import android.view.MotionEvent; import android.widget.RelativeLayout; public class CustomLayout extends RelativeLayout { public CustomLayout(Context context, AttributeSet attrs) { super(context, attrs, 0); } public CustomLayout(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } @Override public boolean onTouchEvent(MotionEvent event) { return super.onTouchEvent(event); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { return true; } }
我們就重寫了onInterceptTouchEvent(),返回true, RelativeLayout默認(rèn)是返回false, 然后再CustomLayout布局中加一個Button ,如下圖
我們這次不從DecorView的dispatchTouchEvent()分析了,直接從CustomLayout的dispatchTouchEvent()分析
我們先看ACTION_DOWN 來到25行,由于我們重寫了onInterceptTouchEvent()返回true, 所以不走這個if里面,直接往下看代碼,來到84行, target為null,所以進(jìn)入if方法里面,直接調(diào)用super.dispatchTouchEvent()方法, 也就是View的dispatchTouchEvent()方法,而在View的dispatchTouchEvent()方法中是直接調(diào)用View的onTouchEvent()方法,但是CustomLayout重寫了onTouchEvent(),所以這里還是調(diào)用CustomLayout的onTouchEvent(), 這個方法返回false, 不消費Touch事件,所以不會在觸發(fā)ACTION_MOVE,ACTION_UP等事件了,這里我再畫一個流程圖吧(含有onInterceptTouchEvent()方法的)
好了,就分析到這里吧,差不多分析完了,還有一種情況沒有分析到,例如我將CustomLayout的代碼改成下面的情形,Touch事件又是怎么分發(fā)的呢?我這里就不帶大家分析了
package com.example.androidtouchevent; import android.content.Context; import android.util.AttributeSet; import android.view.MotionEvent; import android.widget.RelativeLayout; public class CustomLayout extends RelativeLayout { public CustomLayout(Context context, AttributeSet attrs) { super(context, attrs, 0); } public CustomLayout(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } @Override public boolean onTouchEvent(MotionEvent event) { return super.onTouchEvent(event); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { if(ev.getAction() == MotionEvent.ACTION_MOVE){ return true; } return super.onInterceptTouchEvent(ev); } }
這篇文章的篇幅有點長,如果你想了解Touch事件的分發(fā)機(jī)制,你一定要認(rèn)真看完,下面來總結(jié)一下吧
1.Activity的最頂層Window是PhoneWindow,PhoneWindow的最頂層View是DecorView
2.一個clickable或者longClickable的View會永遠(yuǎn)消費Touch事件,不管他是enabled還是disabled的
3.View的長按事件是在ACTION_DOWN中執(zhí)行,要想執(zhí)行長按事件該View必須是longClickable的,并且不能產(chǎn)生ACTION_MOVE
4.View的點擊事件是在ACTION_UP中執(zhí)行,想要執(zhí)行點擊事件的前提是消費了ACTION_DOWN和ACTION_MOVE,并且沒有設(shè)置OnLongClickListener的情況下,如設(shè)置了OnLongClickListener的情況,則必須使onLongClick()返回false
5.如果View設(shè)置了onTouchListener了,并且onTouch()方法返回true,則不執(zhí)行View的onTouchEvent()方法,也表示View消費了Touch事件,返回false則繼續(xù)執(zhí)行onTouchEvent()
6.Touch事件是從最頂層的View一直分發(fā)到手指touch的最里層的View,如果最里層View消費了ACTION_DOWN事件(設(shè)置onTouchListener,并且onTouch()返回true 或者onTouchEvent()方法返回true)才會觸發(fā)ACTION_MOVE,ACTION_UP的發(fā)生,如果某個ViewGroup攔截了Touch事件,則Touch事件交給ViewGroup處理
7.Touch事件的分發(fā)過程中,如果消費了ACTION_DOWN,而在分發(fā)ACTION_MOVE的時候,某個ViewGroup攔截了Touch事件,就像上面那個自定義CustomLayout,則會將ACTION_CANCEL分發(fā)給該ViewGroup下面的Touch到的View,然后將Touch事件交給ViewGroup處理,并返回true
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。