Android View事件派發機制詳解與源碼分析

參考的文章有:
http://blog.csdn.net/yanbober/article/details/45887547
http://ztelur.github.io/2016/02/04/%E5%9B%BE%E8%A7%A3Android%E4%BA%8B%E4%BB%B6%E4%BC%A0%E9%80%92%E4%B9%8BView%E7%AF%87/
http://blog.csdn.net/guolin_blog/article/details/9097463
http://wangkuiwu.github.io/
http://blog.csdn.net/cyp331203/article/details/45071069

2 基礎實例現象

2-1 例子

從一個例子分析說起吧。如下是一個很簡單不過的Android實例:
這裏寫圖片描述

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/mylayout"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:gravity="center"
    android:orientation="vertical" >

    <Button
        android:id="@+id/my_btn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="click test" />

</LinearLayout>
public class MainActivity extends Activity implements View.OnTouchListener, View.OnClickListener {

    public static final String TAG = "MainActivity";
    private LinearLayout mLayout;
    private Button mButton;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        mLayout = (LinearLayout) this.findViewById(R.id.mylayout);
        mButton = (Button) this.findViewById(R.id.my_btn);

        mLayout.setOnTouchListener(this);
        mButton.setOnTouchListener(this);

        mLayout.setOnClickListener(this);
        mButton.setOnClickListener(this);
    }

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            Log.i(TAG, "OnTouchListener--onTouch-- action_down --" + v);
            break;
        case MotionEvent.ACTION_MOVE:
            Log.i(TAG, "OnTouchListener--onTouch-- action_move --" + v);
            break;
        case MotionEvent.ACTION_UP:
            Log.i(TAG, "OnTouchListener--onTouch-- action_up --" + v);
            break;
        }
        return false;
    }

    @Override
    public void onClick(View v) {
        Log.i(TAG, "OnClickListener--onClick--" + v);
    }
}

2-2 現象

  1. 當穩穩的點擊Button時打印如下:
    這裏寫圖片描述
  2. 當穩穩的點擊除過Button以外的其他地方時打印如下
    這裏寫圖片描述
  3. 當手指點擊Button時按在Button上晃動了一下鬆開後的打印如下
    這裏寫圖片描述

現在我們來分析一下上面的情況:

 onTouch方法裏能做的事情比onClick要多一些,比如判斷手指按下、擡起、移動等事件,那麼如果我兩個事件都註冊了也,onTouch是優先於onClick執行的,並且onTouch執行了兩次,如果你的手指按在上面左右移動一下onTouch會執行更多次,因此事件傳遞的順序是先經過onTouch,再傳遞到onClick。

細心的朋友應該可以注意到,onTouch方法是有返回值的,這裏我們返回的是false,如果我們嘗試把onTouch方法裏的返回值改成true,再運行一次,再次點擊Button結果如下:

@Override
    public boolean onTouch(View v, MotionEvent event) {
        switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
        Log.i(TAG, "OnTouchListener--onTouch-- action_down --" + v);
            break;
        case MotionEvent.ACTION_MOVE:
        Log.i(TAG, "OnTouchListener--onTouch-- action_move --" + v);
            break;
        case MotionEvent.ACTION_UP:
        Log.i(TAG, "OnTouchListener--onTouch-- action_up --" + v);
            break;
        }
        return true;
    }

運行結果:
這裏寫圖片描述

我們發現,onClick方法不再執行了!爲什麼會這樣呢?你可以先理解成onTouch方法返回true就認爲這個事件被onTouch消費掉了,因而不會再繼續向下傳遞。

2-3 總結結論

好了,經過這個簡單的實例驗證你可以總結髮現:

  • Android控件的Listener事件觸發順序是先觸發onTouch,其次onClick。
  • 如果控件的onTouch返回true將會阻止事件繼續傳遞,返回false事件會繼續傳遞。

3、現在我們來分析一下View與ViewGroup之間的關係

如下是幾個繼承關係圖:
這裏寫圖片描述

看了官方這個繼承圖是不是明白了上面例子中說的LinearLayout是ViewGroup的子類,ViewGroup是View的子類,Button是View的子類關係呢?其實,在Android中所有的控件無非都是ViewGroup或者View的子類,說高尚點就是所有控件都是View的子類。通過繼承關係是說明一切控件都是View,同時View與ViewGroup又存在一些區別,所以該模塊才只單單先分析View觸摸屏事件傳遞機制

首先你需要知道一點,只要你觸摸到了任何一個控件,就一定會調用該控件的dispatchTouchEvent方法。那當我們去點擊按鈕的時候,就會去調用Button類裏的dispatchTouchEvent方法,可是你會發現Button類裏並沒有這個方法,那麼就到它的父類TextView裏去找一找,你會發現TextView裏也沒有這個方法,那沒辦法了,只好繼續在TextView的父類View裏找一找,這個時候你終於在View裏找到了這個方法,示意圖如下:
這裏寫圖片描述

然後我們來看一下View中dispatchTouchEvent方法的源碼:

/**
     * Pass the touch screen motion event down to the target view, or this
     * view if it is the target.
     *
     * @param event The motion event to be dispatched.
     * @return True if the event was handled by the view, false otherwise.
     */
    public boolean dispatchTouchEvent(MotionEvent event) {
        // If the event should be handled by accessibility focus first.
        if (event.isTargetAccessibilityFocus()) {
            // We don't have focus or no virtual descendant has it, do not handle the event.
            if (!isAccessibilityFocusedViewOrHost()) {
                return false;
            }
            // We have focus and got the event, then use normal event dispatch.
            event.setTargetAccessibilityFocus(false);
        }

        boolean result = false;

        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onTouchEvent(event, 0);
        }

        final int actionMasked = event.getActionMasked();
        if (actionMasked == MotionEvent.ACTION_DOWN) {
            // Defensive cleanup for new gesture
            stopNestedScroll();
        }
    // 如果該View被遮蔽,並且該View在被遮蔽時不響應點擊事件;
    // 此時,返回false;不會執行onTouch()或onTouchEvent(),即過濾調用該點擊事件。
    // 否則,返回true。
    // 被遮蔽的意思是:該View不是位於頂部,有其他的View在它之上。
        if (onFilterTouchEventForSecurity(event)) {
            //noinspection SimplifiableIfStatement
            ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnTouchListener != null
                    && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) {
                result = true;
            }

            if (!result && onTouchEvent(event)) {
                result = true;
            }
        }

        if (!result && mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
        }

        // Clean up after nested scrolls if this is the end of a gesture;
        // also cancel it if we tried an ACTION_DOWN but we didn't want the rest
        // of the gesture.
        if (actionMasked == MotionEvent.ACTION_UP ||
                actionMasked == MotionEvent.ACTION_CANCEL ||
                (actionMasked == MotionEvent.ACTION_DOWN && !result)) {
            stopNestedScroll();
        }

        return result;
    }

dispatchTouchEvent的代碼有點長,我們通過流程圖來進行看:
這裏寫圖片描述

  • 首先在第10行,判斷當前View是否爲事件,如果是false返回false,true就往下執行。
  • 第21行,只是一個輸入法一致的處理,並不影響返回的結果,這裏不作分析,往下走重點
  • 到31行的if (onFilterTouchEventForSecurity(event))語句判斷當前View是否沒被遮住
  • 33行,ListenerInfo局部變量,ListenerInfo是View的靜態內部類,用來定義一堆關於View的XXXListener等方法;
if (li != null && li.mOnTouchListener != null
      && (mViewFlags & ENABLED_MASK) == ENABLED
       && li.mOnTouchListener.onTouch(this, event)) {
                result = true;
            }

首先li對象自然不會爲null,li.mOnTouchListener呢?你會發現ListenerInfo的mOnTouchListener成員是在哪兒賦值的呢?怎麼確認他是不是null呢?通過在View類裏搜索可以看到

 /**
     * Register a callback to be invoked when a touch event is sent to this view.
     * @param l the touch listener to attach to this view
     */
    public void setOnTouchListener(OnTouchListener l) {
        getListenerInfo().mOnTouchListener = l;
    }

第一:上面的實例中我們是設置過Button的setOnTouchListener方法的,所以也不爲null
第二:(mViewFlags & ENABLED_MASK) == ENABLED是判斷當前點擊的控件是否是enable的,按鈕默認都是enable的,因此這個條件恆定爲true
第三:這個比較關鍵了,mOnTouchListener.onTouch(this, event),其實也就是去回調控件註冊touch事件時的onTouch方法。也就是說如果我們在onTouch方法裏返回true,就會讓這三個條件全部成立,從而整個方法直接返回true。如果我們在onTouch方法裏返回false,就會再去執行onTouchEvent(event)方法

結論:
  • 首先在dispatchTouchEvent中最先執行的就是onTouch方法,因此onTouch肯定是要優先於onClick執行的,也是印證了剛剛的打印結果。
  • 而如果在onTouch方法裏返回了true,就會讓dispatchTouchEvent方法直接返回true,不會再繼續往下執行。而打印結果也證實瞭如果onTouch返回true,onClick就不會再執行了
  • 如果只要沒有設置touchListener或者不是ENABLEDY會返回false就會執行onTouchEvent

      onClick一定與onTouchEvent有關係,onClick的調用肯定是在onTouchEvent(event)方法中的,接下來就分析分析dispatchTouchEvent方法中的onTouchEvent方法。
    

可以參考onTouchEvent事件的流程圖:
http://ztelur.github.io/2016/02/04/%E5%9B%BE%E8%A7%A3Android%E4%BA%8B%E4%BB%B6%E4%BC%A0%E9%80%92%E4%B9%8BView%E7%AF%87/
View的dispatchTouchEvent中的onTouchEvent源碼:

    public boolean onTouchEvent(MotionEvent event) {
        final float x = event.getX();
        final float y = event.getY();
        final int viewFlags = mViewFlags;
        final int action = event.getAction();

        if ((viewFlags & ENABLED_MASK) == DISABLED) {
            if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
                setPressed(false);
            }
            // A disabled view that is clickable still consumes the touch
            // events, it just doesn't respond to them.
            return (((viewFlags & CLICKABLE) == CLICKABLE
                    || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
                    || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE);
        }

        if (mTouchDelegate != null) {
            if (mTouchDelegate.onTouchEvent(event)) {
                return true;
            }
        }

        if (((viewFlags & CLICKABLE) == CLICKABLE ||
                (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
                (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {
            switch (action) {
                case MotionEvent.ACTION_UP:
                    boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
                    if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
                        // take focus if we don't have it already and we should in
                        // touch mode.
                        boolean focusTaken = false;
                        if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
                            focusTaken = requestFocus();
                        }

                        if (prepressed) {
                            // The button is being released before we actually
                            // showed it as pressed.  Make it show the pressed
                            // state now (before scheduling the click) to ensure
                            // the user sees it.
                            setPressed(true, x, y);
                       }

                        if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
                            // This is a tap, so remove the longpress check
                            removeLongPressCallback();

                            // Only perform take click actions if we were in the pressed state
                            if (!focusTaken) {
                                // Use a Runnable and post this rather than calling
                                // performClick directly. This lets other visual state
                                // of the view update before click actions start.
                                if (mPerformClick == null) {
                                    mPerformClick = new PerformClick();
                                }
                                if (!post(mPerformClick)) {
                                    performClick();
                                }
                            }
                        }

                        if (mUnsetPressedState == null) {
                            mUnsetPressedState = new UnsetPressedState();
                        }

                        if (prepressed) {
                            postDelayed(mUnsetPressedState,
                                    ViewConfiguration.getPressedStateDuration());
                        } else if (!post(mUnsetPressedState)) {
                            // If the post failed, unpress right now
                            mUnsetPressedState.run();
                        }

                        removeTapCallback();
                    }
                    mIgnoreNextUpEvent = false;
                    break;

                case MotionEvent.ACTION_DOWN:
                    mHasPerformedLongPress = false;

                    if (performButtonActionOnTouchDown(event)) {
                        break;
                    }

                    // Walk up the hierarchy to determine if we're inside a scrolling container.
                    boolean isInScrollingContainer = isInScrollingContainer();

                    // For views inside a scrolling container, delay the pressed feedback for
                    // a short period in case this is a scroll.
                    if (isInScrollingContainer) {
                        mPrivateFlags |= PFLAG_PREPRESSED;
                        if (mPendingCheckForTap == null) {
                            mPendingCheckForTap = new CheckForTap();
                        }
                        mPendingCheckForTap.x = event.getX();
                        mPendingCheckForTap.y = event.getY();
                        postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
                    } else {
                        // Not inside a scrolling container, so show the feedback right away
                        setPressed(true, x, y);
                        checkForLongClick(0);
                    }
                    break;

                case MotionEvent.ACTION_CANCEL:
                    setPressed(false);
                    removeTapCallback();
                    removeLongPressCallback();
                    mInContextButtonPress = false;
                    mHasPerformedLongPress = false;
                    mIgnoreNextUpEvent = false;
                    break;

                case MotionEvent.ACTION_MOVE:
                    drawableHotspotChanged(x, y);

                    // Be lenient about moving outside of buttons
                    if (!pointInView(x, y, mTouchSlop)) {
                        // Outside button
                        removeTapCallback();
                        if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
                            // Remove any future long press/tap checks
                            removeLongPressCallback();

                            setPressed(false);
                        }
                    }
                    break;
            }

            return true;
        }

        return false;
    }

首先、6到14行可以看出,如果控件(View)是disenable狀態,(並且是可以clickable的或者是長按等則onTouchEvent直接消費事件返回true,),關於控件的enable或者clickable屬性可以通過java或者xml直接設置.
第二,上面的條件不滿足的情況下就會進入到MotionEvent.ACTION_UP:裏面,判斷了是否按下過,同時是不是可以得到焦點,然後嘗試獲取焦點,然後判斷如果不是longPressed則通過post在UI Thread中執行一個PerformClick的Runnable,也就是performClick方法。具體如下:

 public boolean performClick() {
        final boolean result;
        final ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnClickListener != null) {
            playSoundEffect(SoundEffectConstants.CLICK);
            li.mOnClickListener.onClick(this);
            result = true;
        } else {
            result = false;
        }

        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
        return result;
    }

這個方法也是先定義一個ListenerInfo的變量然後賦值,接着判斷li.mOnClickListener是不是爲null,決定執行不執行onClick。你指定現在已經很機智了,和onTouch一樣,搜一下mOnClickListener在哪賦值的唄,結果發現:

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

看見了吧!控件只要監聽了onClick方法則mOnClickListener就不爲null,而且有意思的是如果調運setOnClickListener方法設置監聽且控件是disclickable的情況下默認會幫設置爲clickable。

onClick就在onTouchEvent中執行的,而且是在onTouchEvent的ACTION_UP事件中執行的。

 總結:
  • onTouchEvent方法中會在ACTION_UP分支中觸發onClick的監聽
  • 當dispatchTouchEvent在進行事件分發的時候,只有前一個action返回true,纔會觸發下一個action
    解釋一下:
    如果在onTouch方法中的執行ACTION_DOWN的時候返回了false,後面一系列其它的action就不會再得到執行了。簡單的說,就是當dispatchTouchEvent在進行事件分發的時候,只有前一個action返回true(意思就是要在dispatchtouchEvent方法裏面到的onTouchEvent調用之前result要爲true),纔會觸發後一個action(就是執行onTouchEvent(event)方法裏面的action_down,up,move這些)。

    解惑:
    

    很多的朋友肯定要有巨大的疑問了。這不是在自相矛盾嗎?前面的例子中,明明在onTouch事件裏面返回了false,ACTION_DOWN和ACTION_UP不是都得到執行了嗎?其實你只是被假象所迷惑了,讓我們仔細分析一下,在前面的例子當中,我們到底返回的是什麼。
    參考着我們前面分析的源碼,首先在onTouch事件裏返回了false,就一定會進入到onTouchEvent方法中,然後我們來看一下onTouchEvent方法的細節。由於我們點擊了按鈕,就會進入到第14行這個if判斷的內部,然後你會發現,不管當前的action是什麼,最終都一定會走到第89行,返回一個true。
    是不是有一種被欺騙的感覺?明明在onTouch事件裏返回了false,系統還是在onTouchEvent方法中幫你返回了true。就因爲這個原因,才使得前面的例子中ACTION_UP可以得到執行。

    1. onTouch和onTouchEvent有什麼區別,又該如何使用?
      從源碼中可以看出,這兩個方法都是在View的dispatchTouchEvent中調用的,onTouch優先於onTouchEvent執行。如果在onTouch方法中通過返回true將事件消費掉,onTouchEvent將不會再執行。
      另外需要注意的是,onTouch能夠得到執行需要兩個前提條件,第一mOnTouchListener的值不能爲空,第二當前點擊的控件必須是enable的。因此如果你有一個控件是非enable的,那麼給它註冊onTouch事件將永遠得不到執行。對於這一類控件,如果我們想要監聽它的touch事件,就必須通過在該控件中重寫onTouchEvent方法來實現。
    2. 爲什麼給ListView引入了一個滑動菜單的功能,ListView就不能滾動了?
      如果你閱讀了Android滑動框架完全解析,教你如何一分鐘實現滑動菜單特效 這篇文章,你應該會知道滑動菜單的功能是通過給ListView註冊了一個touch事件來實現的。如果你在onTouch方法裏處理完了滑動邏輯後返回true,那麼ListView本身的滾動事件就被屏蔽了,自然也就無法滑動(原理同前面例子中按鈕不能點擊),因此解決辦法就是在onTouch方法裏返回false。
    3. 爲什麼圖片輪播器裏的圖片使用Button而不用ImageView?
      提這個問題的朋友是看過了Android實現圖片滾動控件,含頁籤功能,讓你的應用像淘寶一樣炫起來 這篇文章。當時我在圖片輪播器裏使用Button,主要就是因爲Button是可點擊的,而ImageView是不可點擊的。如果想要使用ImageView,可以有兩種改法。第一,在ImageView的onTouch方法裏返回true,這樣可以保證ACTION_DOWN之後的其它action都能得到執行,才能實現圖片滾動的效果。第二,在佈局文件裏面給ImageView增加一個android:clickable=”true”的屬性,這樣ImageView變成可點擊的之後,即使在onTouch裏返回了false,ACTION_DOWN之後的其它action也是可以得到執行的。

4、透過源碼繼續進階實例驗證

4-1 例子

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/mylayout"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:gravity="center"
    android:orientation="vertical" >

    <com.example.viewdispatch.TestButton
        android:id="@+id/my_btn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="click test" />

</LinearLayout>
public class TestButton extends Button {

    public TestButton(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            Log.i(ListenerActivity.TAG, "dispatchTouchEvent-- action_down --");
            break;
        case MotionEvent.ACTION_MOVE:
            Log.i(ListenerActivity.TAG, "dispatchTouchEvent--- action_move --");
            break;
        case MotionEvent.ACTION_UP:
            Log.i(ListenerActivity.TAG, "dispatchTouchEvent-- action_up --");
            break;
        }
        return super.dispatchTouchEvent(event);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            Log.i(ListenerActivity.TAG, "onTouchEvent-- action_down --");
            break;
        case MotionEvent.ACTION_MOVE:
            Log.i(ListenerActivity.TAG, "onTouchEvent-- action_move --");
            break;
        case MotionEvent.ACTION_UP:
            Log.i(ListenerActivity.TAG, "onTouchEvent--action_up --");
            break;
        }
        return super.onTouchEvent(event);
    }
}
public class ListenerActivity extends Activity implements View.OnTouchListener, View.OnClickListener {

    public static final String TAG = "ListenerActivity";

    private LinearLayout mLayout;
    private TestButton mButton;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.listener);

        mLayout = (LinearLayout) this.findViewById(R.id.mylayout);
        mButton = (TestButton) this.findViewById(R.id.my_btn);

        mLayout.setOnTouchListener(this);
        mButton.setOnTouchListener(this);

        mLayout.setOnClickListener(this);
        mButton.setOnClickListener(this);
    }

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            Log.i(TAG, "OnTouchListener--onTouch-- action_down --" + v);
            break;
        case MotionEvent.ACTION_MOVE:
            Log.i(TAG, "OnTouchListener--onTouch-- action_move --" + v);
            break;
        case MotionEvent.ACTION_UP:
            Log.i(TAG, "OnTouchListener--onTouch-- action_up --" + v);
            break;
        }
        return false;
    }

    @Override
    public void onClick(View v) {
        Log.i(TAG, "OnClickListener--onClick--" + v);
    }
}

4-2 現象分析
4-2-1 點擊Button(手抽筋了一下)
這裏寫圖片描述

分析上面發現:

  • dispatchTouchEvent方法先派發down事件,完事調用onTouch的down事件,完事調用onTouchEvent返回true,同時dispatchTouchEvent返回true,
  • 然後dispatchTouchEvent繼續派發move或者up事件,循環,直到onTouchEvent處理up事件時調運onClick事件,完事返回true,同時dispatchTouchEvent返回true;一次完整的View事件派發流程結束。

4-2-2 簡單修改onTouchEvent返回值爲true

@Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
        Log.i(ListenerActivity.TAG, "onTouchEvent-- action_down --");
            break;
        case MotionEvent.ACTION_MOVE:
        Log.i(ListenerActivity.TAG, "onTouchEvent-- action_move --");
            break;
        case MotionEvent.ACTION_UP:
        Log.i(ListenerActivity.TAG, "onTouchEvent--action_up --");
            break;
        }
        return true;
    }

這裏寫圖片描述

分析結果:
可以發現,當自定義了控件(View)的onTouchEvent直接返回true而不調運super方法時,事件派發機制如同4.2.1類似,只是最後up事件沒有觸發onClick而已(因爲沒有調用super),解釋:因爲我們重寫了父類的onTouchEvent方法,也就是根部View的onTouchEvent方法,然後在dispatchTouchEvent的時候調用的就是我們重寫的onTouchEvent方法,由於onClick方法是在onTouchEvent裏面調用了,我們重寫了沒有調用performClick方法,所以onClick方法沒有調用。

所以可想而知,如果TestButton類的onTouchEvent修改爲如下:

@Override
    public boolean onTouchEvent(MotionEvent event) {
        super.onTouchEvent(event);
        switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
        Log.i(ListenerActivity.TAG, "onTouchEvent-- action_down --");
            break;
        case MotionEvent.ACTION_MOVE:
        Log.i(ListenerActivity.TAG, "onTouchEvent-- action_move --");
            break;
        case MotionEvent.ACTION_UP:
        Log.i(ListenerActivity.TAG, "onTouchEvent--action_up --");
            break;
        }
        return true;
    }

這裏寫圖片描述

分析:這個的運行效果和第一個效果是一樣的。沒有什麼區別

4-2-3 簡單修改onTouchEvent返回值爲false

@Override
    public boolean onTouch(View v, MotionEvent event) {
        switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            Log.i(TAG, "OnTouchListener--onTouch-- action_down --" + v);
            break;
        case MotionEvent.ACTION_MOVE:
            Log.i(TAG, "OnTouchListener--onTouch-- action_move --" + v);
            break;
        case MotionEvent.ACTION_UP:
            Log.i(TAG, "OnTouchListener--onTouch-- action_up --" + v);
            break;
        }
        return false;
    }

這裏寫圖片描述

分析:你會發現如果onTouchEvent返回false(也即dispatchTouchEvent一旦返回false將不再繼續派發其他action,立即停止派發),這裏只派發了down事件,後面的up,move就都沒有觸發了。至於後面觸發了LinearLayout的touch與click事件我們這裏不做關注,下一篇博客會詳細解釋爲啥(其實你可以想下的,LinearLayout是ViewGroup的子類,你懂的),這裏你只用知道View的onTouchEvent返回false會阻止繼續派發事件
同理修改如下:

@Override
    public boolean onTouchEvent(MotionEvent event) {
        super.onTouchEvent(event);
        switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
        Log.i(ListenerActivity.TAG, "onTouchEvent-- action_down --");
            break;
        case MotionEvent.ACTION_MOVE:
        Log.i(ListenerActivity.TAG, "onTouchEvent-- action_move --");
            break;
        case MotionEvent.ACTION_UP:
        Log.i(ListenerActivity.TAG, "onTouchEvent--action_up --");
            break;
        }
        return false;
    }

這裏寫圖片描述

4-2-4 簡單修改dispatchTouchEvent返回值爲true

將TestButton類的dispatchTouchEvent方法修改如下,其他和基礎代碼保持不變:

@Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
        Log.i(ListenerActivity.TAG, "dispatchTouchEvent-- action_down --");
            break;
        case MotionEvent.ACTION_MOVE:
        Log.i(ListenerActivity.TAG, "dispatchTouchEvent--- action_move --");
            break;
        case MotionEvent.ACTION_UP:
        Log.i(ListenerActivity.TAG, "dispatchTouchEvent-- action_up --");
            break;
        }
        return true;
    }

這裏寫圖片描述

分析:你會發現如果dispatchTouchEvent直接返回true且不調運super任何事件都得不到觸發,onTouch和onTouchEvent都不觸發了

繼續修改如下呢?
將TestButton類的dispatchTouchEvent方法修改如下,其他和基礎代碼保持不變:

@Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        super.dispatchTouchEvent(event);
        switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
        Log.i(ListenerActivity.TAG, "dispatchTouchEvent-- action_down --");
            break;
        case MotionEvent.ACTION_MOVE:
        Log.i(ListenerActivity.TAG, "dispatchTouchEvent--- action_move --");
            break;
        case MotionEvent.ACTION_UP:
        Log.i(ListenerActivity.TAG, "dispatchTouchEvent-- action_up --");
            break;
        }
        return true;
    }

這裏寫圖片描述

可以發現所有事件都可以得到正常派發,和4.2.1類似。

4-2-5 簡單修改dispatchTouchEvent返回值爲false

將TestButton類的dispatchTouchEvent方法修改如下,其他和基礎代碼保持不變:

@Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            Log.i(ListenerActivity.TAG, "dispatchTouchEvent-- action_down --");
            break;
        case MotionEvent.ACTION_MOVE:
            Log.i(ListenerActivity.TAG, "dispatchTouchEvent--- action_move --");
            break;
        case MotionEvent.ACTION_UP:
            Log.i(ListenerActivity.TAG, "dispatchTouchEvent-- action_up --");
            break;
        }
        return false;
    }

點擊Button如下:
這裏寫圖片描述

你會發現事件不進行任何繼續觸發,關於點擊Button觸發了LinearLayout的事件暫時不用關注,下篇詳解。

繼續修改如下呢?
將TestButton類的dispatchTouchEvent方法修改如下,其他和基礎代碼保持不變:

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
        Log.i(ListenerActivity.TAG, "dispatchTouchEvent-- action_down --");
            break;
        case MotionEvent.ACTION_MOVE:
        Log.i(ListenerActivity.TAG, "dispatchTouchEvent--- action_move --");
            break;
        case MotionEvent.ACTION_UP:
        Log.i(ListenerActivity.TAG, "dispatchTouchEvent-- action_up --");
            break;
        }
        super.dispatchTouchEvent(event);
        return false;
    }

點擊Button如下:
這裏寫圖片描述

你會發現結果和4.2.3的第二部分結果一樣,也就是說如果dispatchTouchEvent返回false事件將不再繼續派發下一次。

4-2-6 簡單修改dispatchTouchEvent與onTouchEvent返回值

修改dispatchTouchEvent返回值爲true,onTouchEvent爲false:

將TestButton類的dispatchTouchEvent方法和onTouchEvent方法修改如下,其他和基礎代碼保持不變:

@Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            Log.i(ListenerActivity.TAG, "dispatchTouchEvent-- action_down --");
            break;
        case MotionEvent.ACTION_MOVE:
            Log.i(ListenerActivity.TAG, "dispatchTouchEvent--- action_move --");
            break;
        case MotionEvent.ACTION_UP:
            Log.i(ListenerActivity.TAG, "dispatchTouchEvent-- action_up --");
            break;
        }
        super.dispatchTouchEvent(event);
        return true;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            Log.i(ListenerActivity.TAG, "onTouchEvent-- action_down --");
            break;
        case MotionEvent.ACTION_MOVE:
            Log.i(ListenerActivity.TAG, "onTouchEvent-- action_move --");
            break;
        case MotionEvent.ACTION_UP:
            Log.i(ListenerActivity.TAG, "onTouchEvent--action_up --");
            break;
        }
        super.onTouchEvent(event);
        return false;
    }

這裏寫圖片描述

修改dispatchTouchEvent返回值爲false,onTouchEvent爲true:

將TestButton類的dispatchTouchEvent方法和onTouchEvent方法修改如下,其他和基礎代碼保持不變:

@Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            Log.i(ListenerActivity.TAG, "dispatchTouchEvent-- action_down --");
            break;
        case MotionEvent.ACTION_MOVE:
            Log.i(ListenerActivity.TAG, "dispatchTouchEvent--- action_move --");
            break;
        case MotionEvent.ACTION_UP:
            Log.i(ListenerActivity.TAG, "dispatchTouchEvent-- action_up --");
            break;
        }
        super.dispatchTouchEvent(event);
        return false;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            Log.i(ListenerActivity.TAG, "onTouchEvent-- action_down --");
            break;
        case MotionEvent.ACTION_MOVE:
            Log.i(ListenerActivity.TAG, "onTouchEvent-- action_move --");
            break;
        case MotionEvent.ACTION_UP:
            Log.i(ListenerActivity.TAG, "onTouchEvent--action_up --");
            break;
        }
        super.onTouchEvent(event);
        return true;
    }

這裏寫圖片描述

由此對比得出結論,dispatchTouchEvent事件派發是傳遞的,如果返回值爲false將停止下次事件派發,如果返回true將繼續下次派發。譬如,當前派發down事件,如果返回true則繼續派發up,如果返回false派發完down就停止了。

5 總結View觸摸屏事件傳遞機制

上面例子也測試了,源碼也分析了,總得有個最終結論方便平時寫代碼作爲參考依據呀,不能每次都再去分析一遍源碼,那得多蛋疼呢!

綜合得出Android View的觸摸屏事件傳遞機制有如下特徵:

  1. 觸摸控件(View)首先執行dispatchTouchEvent方法。
  2. 在dispatchTouchEvent方法中先執行onTouch方法,後執行onClick方法(onClick方法在onTouchEvent中執行,下面會分析)。
  3. 如果控件(View)的onTouch返回false或者mOnTouchListener爲null(控件沒有設置setOnTouchListener方法)或者控件不是enable的情況下會調運onTouchEvent,dispatchTouchEvent返回值與onTouchEvent返回一樣。
  4. 如果控件不是enable的設置了onTouch方法也不會執行,只能通過重寫控件的onTouchEvent方法處理(上面已經處理分析了),dispatchTouchEvent返回值與onTouchEvent返回一樣。
  5. 如果控件(View)是enable且onTouch返回true情況下,dispatchTouchEvent直接返回true,不會調用onTouchEvent方法。
  6. 當dispatchTouchEvent在進行事件分發的時候,只有前一個action返回true,纔會觸發下一個action(也就是說dispatchTouchEvent返回true纔會進行下一次action派發)。

關於上面的疑惑還有ViewGroup事件派發機制你可以繼續閱讀下一篇博客

【工匠若水 http://blog.csdn.net/yanbober

發佈了183 篇原創文章 · 獲贊 32 · 訪問量 42萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章