天天看點

Andriod 從源碼的角度詳解View,ViewGroup的Touch事件的分發機制

轉載請注明本文出自xiaanming的部落格(http://blog.csdn.net/xiaanming/article/details/21696315),請尊重他人的辛勤勞動成果,謝謝!

今天這篇文章主要分析的是Android的事件分發機制,采用例子加源碼的方式讓大家深刻的了解Android事件分發的具體情況,雖然網上很多Android的事件分發的文章,有些還寫的不錯,但是我還是決定寫這篇文章,用我自己的思維方式來幫助大家了解Android事件分發,Android事件分發到底有多重要呢?相信很多Android開發者都明白吧,這個我就不介紹了,我也寫了很多篇文章裡面涉及到Android的事件處理的問題,可能不了解Android事件分發的朋友會有點難了解吧,不過沒關系,相信看了這篇文章的你會對Android事件分發有進一步的了解。我這篇文章分析的源碼是Android 2.2的源碼, 也許你會說,幹嘛不去分析最新的源碼呢?我這裡要解釋一下,Android 2.2的源碼跟最新的源碼在功能效果方面是一樣的,隻不過最新的源碼相對于Android 2.2來說更加健壯一些, Android 2.2的事件處理的代碼幾乎都寫在一個方法體裡面,而最新的源碼分了很多個方法寫,如果用最新的源碼調用方法會繞來繞去的,相信你看的也會暈,出于這個考慮,我就拿Android 2.2的源碼來給大家分析。

ViewGroup的事件分發機制

我們用手指去觸摸Android手機螢幕,就會産生一個觸摸事件,但是這個觸摸事件在底層是怎麼分發的呢?這個我還真不知道,這裡涉及到操作硬體(手機螢幕)方面的知識,也就是Linux核心方面的知識,我也沒有了解過這方面的東西,是以我們可能就往上層來分析分析,我們知道Android中負責與使用者互動,與使用者操作緊密相關的四大元件之一是Activity, 是以我們有理由相信Activity中存在分發事件的方法,這個方法就是dispatchTouchEvent(),我們先看其源碼吧

public boolean dispatchTouchEvent(MotionEvent ev) {

		//如果是按下狀态就調用onUserInteraction()方法,onUserInteraction()方法
		//是個空的方法, 我們直接跳過這裡看下面的實作
		if (ev.getAction() == MotionEvent.ACTION_DOWN) {
			onUserInteraction();
		}
		
		if (getWindow().superDispatchTouchEvent(ev)) {
			return true;
		}
		
		//getWindow().superDispatchTouchEvent(ev)傳回false,這個事件就交給Activity
		//來處理, Activity的onTouchEvent()方法直接傳回了false
		return onTouchEvent(ev);
	}
           

這個方法中我們還是比較關心getWindow()的superDispatchTouchEvent()方法,getWindow()傳回目前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()方法的具體邏輯實作,Window的唯一子類是PhoneWindow,我們就看看PhoneWindow的superDispatchTouchEvent()方法

public boolean superDispatchTouchEvent(KeyEvent event) {
        return mDecor.superDispatcTouchEvent(event);
    }
           

裡面直接調用DecorView類的superDispatchTouchEvent()方法,或許很多人不了解DecorView這個類,DecorView是PhoneWindow的一個final的内部類并且繼承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的層次結構,如下圖

Andriod 從源碼的角度詳解View,ViewGroup的Touch事件的分發機制

我們看到最頂層就是PhoneWindow$DecorView,接着DecorView下面有一個LinearLayout, LinearLayout下面有兩個FrameLayout

上面那個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()方法

public boolean superDispatchTouchEvent(MotionEvent event) {
            return super.dispatchTouchEvent(event);
        }
           

在裡面調用了父類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;

        //這個值預設是false, 然後我們可以通過requestDisallowInterceptTouchEvent(boolean disallowIntercept)方法
        //來改變disallowIntercept的值
        boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;

        //這裡是ACTION_DOWN的處理邏輯
        if (action == MotionEvent.ACTION_DOWN) {
        	//清除mMotionTarget, 每次ACTION_DOWN都很設定mMotionTarget為null
            if (mMotionTarget != null) {
                mMotionTarget = null;
            }

            //disallowIntercept預設是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正在執行動畫, 表示該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;
                            
                            //調用該子View的dispatchTouchEvent()方法
                            if (child.dispatchTouchEvent(ev))  {
                                // 如果child.dispatchTouchEvent(ev)傳回true表示
                            	//該事件被消費了,設定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設定為預設的false
        	//假如我們調用了requestDisallowInterceptTouchEvent()方法來設定disallowIntercept為true
        	//當我們擡起手指或者取消Touch事件的時候要将disallowIntercept重置為false
        	//是以說上面的disallowIntercept預設在我們每次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, 是以我們需要調用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不會執行,隻有ACTION_MOVE
        //ACTION_UP才會走到這裡, 假如在ACTION_MOVE或者ACTION_UP攔截的
        //Touch事件, 将ACTION_CANCEL派發給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事件派發給target
        return target.dispatchTouchEvent(ev);
    }
           

這個方法相對來說還是蠻長,不過所有的邏輯都寫在一起,看起來比較友善,接下來我們就具體來分析一下

我們點選螢幕上面的TextView來看看Touch是如何分發的,先看看ACTION_DOWN

在DecorView這一層會直接調用ViewGroup的dispatchTouchEvent(), 先看18行,每次ACTION_DOWN都會将mMotionTarget設定為null, mMotionTarget是什麼?我們先不管,繼續看代碼,走到25行,  disallowIntercept預設為false,我們再看ViewGroup的onInterceptTouchEvent()方法

public boolean onInterceptTouchEvent(MotionEvent ev) {
        return false;
    }
           

直接傳回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()方法)

Andriod 從源碼的角度詳解View,ViewGroup的Touch事件的分發機制

上面的ViewGroup的Touch事件分發就告一段落先,因為這裡要調用TextView(也就是View)的dispatchTouchEvent()方法,是以我們先分析View的dispatchTouchEvent()方法在将上面的繼續下去

View的Touch事件分發機制

我們還是先看View的dispatchTouchEvent()方法的源碼

public boolean dispatchTouchEvent(MotionEvent event) {
        if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
                mOnTouchListener.onTouch(this, event)) {
            return true;
        }
        return onTouchEvent(event);
    }
           

在這個方法裡面,先進行了一個判斷

第一個條件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如下

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的坐标 = " + event.getX() + " Y的坐标 = " + event.getY());
		
		return super.onTouchEvent(event);
	}

}
           

也可以直接對View設定OnTouchListener接口,在return的時候調用下v.onTouchEvent()

view.setOnTouchListener(new OnTouchListener() {
			
			@Override
			public boolean onTouch(View v, MotionEvent event) {
				
				Log.i("tag", "X的坐标 = " + event.getX() + " Y的坐标 = " + event.getY());
				
				return v.onTouchEvent(event);
			}
		});
           

這樣子也實作了我們所需要的功能,是以我認為OnTouchListener是對onTouchEvent()方法的一個屏蔽和擴充作用,假如你有不一樣的了解,你也可以告訴我下,這裡就不糾結這個了。

我們再看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));
        }

        //如果設定了Touch代理,就交給代理來處理,mTouchDelegate預設是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();

                    //當手指在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()就不執行下面的代碼邏輯直接傳回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是什麼

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()執行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接口,我們看看這個方法的源碼

public void setOnLongClickListener(OnLongClickListener l) {
        if (!isLongClickable()) {
            setLongClickable(true);
        }
        mOnLongClickListener = l;
    }
           

看到沒有,如果這個View不是longClickable的,我們就調用setLongClickable(true)方法設定為longClickable的,是以才會去執行長按方法onLongClick();

要想執行點選事件,這個View就必須要消費ACTION_DOWN和ACTION_MOVE事件,并且沒有設定OnLongClickListener的情況下,如果設定了OnLongClickListener的情況下,需要onLongClick()傳回false才能執行到onClick()方法,也許你又會納悶,一般的View預設是不消費touch事件的,這不是和你上面說的相違背嘛,我們要向執行點選事件必須要調用setOnClickListener()來設定OnClickListener接口,我們看看這個方法的源碼就知道了

public void setOnClickListener(OnClickListener l) {
        if (!isClickable()) {
            setClickable(true);
        }
        mOnClickListener = l;
    }
           

是以說一個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()方法)

Andriod 從源碼的角度詳解View,ViewGroup的Touch事件的分發機制

是以我們點選螢幕上面的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()方法)給大家理清下

Andriod 從源碼的角度詳解View,ViewGroup的Touch事件的分發機制

從上面的流程圖得出一個結論,Touch事件是從頂層的View一直往下分發到手指按下的最裡面的View,如果這個View的onTouchEvent()傳回false,即不消費Touch事件,這個Touch事件就會向上找父布局調用其父布局的onTouchEvent()處理,如果這個View傳回true,表示消費了Touch事件,就不調用父布局的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預設是傳回false, 然後再CustomLayout布局中加一個Button ,如下圖

Andriod 從源碼的角度詳解View,ViewGroup的Touch事件的分發機制

我們這次不從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()方法的)

Andriod 從源碼的角度詳解View,ViewGroup的Touch事件的分發機制

好了,就分析到這裡吧,差不多分析完了,還有一種情況沒有分析到,例如我将CustomLayout的代碼改成下面的情形,Touch事件又是怎麼分發的呢?我這裡就不帶大家分析了

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事件的分發機制,你一定要認真看完,下面來總結一下吧

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