Android View Touch的事件分發機制

開發一段時間的Android,或多或少對Android的事件有一些瞭解,對諸如dispatchTouchEvent、onTouchEvent方法有些瞭解。但真正在面試中被問起,整個機制,或者具體的分析ViewGroup+ViewGroup+View的具體回調順序,就懵了。百度出的第一位博客講解的很到位:
view-touch-dispatch
當一個Touch事件(觸摸事件爲例)到達根節點,即Acitivty的ViewGroup時,它會依次下發,下發的過程是調用子View(ViewGroup)的dispatchTouchEvent方法實現的。簡單來說,就是ViewGroup遍歷它包含着的子View,調用每個View的dispatchTouchEvent方法,而當子View爲ViewGroup時,又會通過調用ViwGroup的dispatchTouchEvent方法繼續調用其內部的View的dispatchTouchEvent方法。上述例子中的消息下發順序是這樣的:①-②-⑤-⑥-⑦-③-④。dispatchTouchEvent方法只負責事件的分發,它擁有boolean類型的返回值,當返回爲true時,順序下發會中斷。在上述例子中如果⑤的dispatchTouchEvent返回結果爲true,那麼⑥-⑦-③-④將都接收不到本次Touch事件。

ViewGroup+ViewGroup+View事件傳遞的具體分析

題目:比如最上面有一個ViewGroup,下面有一個ViewGroup,最下面有一個Button,那麼點擊Button,請問事件是如何傳遞的,這期間會回調哪些方法,順序如何?
看了上面的講解,其實心中已有一定的瞭解。但不能止於此。於是我使用代碼驗證並查看Android的源代碼來看看到底是怎麼回事,來找到最後的答案。
新建Activity,layout的結構爲LinearLayout - RelativeLayout - Button,剛好是題目所示的結構。爲了驗證回調函數,我將這三個View都繼承了,同時也在Activity中覆蓋dispatchTouchEventonTouchEvent

layout佈局如下:

<?xml version="1.0" encoding="utf-8"?>
<com.idengpan.life100.viewtouch.TouchLinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/white"
    android:orientation="vertical" >

    <com.idengpan.life100.viewtouch.TouchRelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent" >

        <com.idengpan.life100.viewtouch.TouchButton
            android:id="@+id/btn_test"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="測試" />
    </com.idengpan.life100.viewtouch.TouchRelativeLayout>

</com.idengpan.life100.viewtouch.TouchLinearLayout>

三個自定義的View/ViewGroup如下:

public class TouchLinearLayout extends LinearLayout {

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

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        System.out.println("TouchLinearLayout onInterceptTouchEvent");
        boolean result = super.onInterceptTouchEvent(ev);
        System.out.println("TouchLinearLayout onInterceptTouchEvent return " + result);
        return result;
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        System.out.println("TouchLinearLayout dispatchTouchEvent");
        boolean result = super.dispatchTouchEvent(ev);
        System.out.println("TouchLinearLayout dispatchTouchEvent return " + result);
        return result;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        System.out.println("TouchLinearLayout onTouchEvent");
        boolean result = super.onTouchEvent(event);
        System.out.println("TouchLinearLayout onTouchEvent return " + result);
        return result;
    }
}

public class TouchRelativeLayout extends RelativeLayout{

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

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        System.out.println("TouchRelativeLayout onInterceptTouchEvent");
        boolean result = super.onInterceptTouchEvent(ev);
        System.out.println("TouchRelativeLayout onInterceptTouchEvent return " + result);
        return result;
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        System.out.println("TouchRelativeLayout dispatchTouchEvent");
        boolean result = super.dispatchTouchEvent(ev);
        System.out.println("TouchRelativeLayout dispatchTouchEvent return " + result);
        return result;
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        System.out.println("TouchRelativeLayout onTouchEvent");
        boolean result = super.onTouchEvent(ev);
        System.out.println("TouchRelativeLayout onTouchEvent return " + result);
        return result;
    }
}

public class TouchButton extends Button {

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


    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        System.out.println("TouchButton dispatchTouchEvent");
        boolean result = super.dispatchTouchEvent(ev);
        System.out.println("TouchButton dispatchTouchEvent return " + result);
        return result;
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        System.out.println("TouchButton onTouchEvent");
        boolean result = super.onTouchEvent(ev);
        System.out.println("TouchButton onTouchEvent return " + result);
        return result;
    }

}

//Activity中覆蓋:
@Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        System.out.println("activity dispatchTouchEvent");
        boolean result = super.dispatchTouchEvent(ev);
        System.out.println("activity dispatchTouchEvent return " + result);
        return result;
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        System.out.println("activity onTouchEvent");
        boolean result = super.onTouchEvent(ev);
        System.out.println("activity onTouchEvent return " + result);
        return result;
    }

點擊Button,可以看到回調的執行順序是:

 I/System.out(7034): activity dispatchTouchEvent
 I/System.out(7034): TouchLinearLayout dispatchTouchEvent
 I/System.out(7034): TouchLinearLayout onInterceptTouchEvent
 I/System.out(7034): TouchLinearLayout onInterceptTouchEvent return false
 I/System.out(7034): TouchRelativeLayout dispatchTouchEvent
 I/System.out(7034): TouchRelativeLayout onInterceptTouchEvent
 I/System.out(7034): TouchRelativeLayout onInterceptTouchEvent return false
 I/System.out(7034): TouchButton dispatchTouchEvent
 I/System.out(7034): TouchButton onTouchEvent
 I/System.out(7034): TouchButton onTouchEvent return true
 I/System.out(7034): TouchButton dispatchTouchEvent return true
 I/System.out(7034): TouchRelativeLayout dispatchTouchEvent return true
 I/System.out(7034): TouchLinearLayout dispatchTouchEvent return true
 I/System.out(7034): activity dispatchTouchEvent return true

看到日誌輸出,更是一目瞭然了。去查看了下ViewGroup的源碼,它的onInterceptTouchEvent方法未做任何處理,總是返回false的。

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

再看看ViewGroup中的dispatchTouchEvent方法:

public boolean dispatchTouchEvent(MotionEvent ev) {
    ....
     // Check for interception.
            final boolean intercepted;
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || mFirstTouchTarget != null) {
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                if (!disallowIntercept) {
                    intercepted = onInterceptTouchEvent(ev);
                    ev.setAction(action); // restore action in case it was changed
                } else {
                    intercepted = false;
                }
            } else {
                // There are no touch targets and this action is not an initial down
                // so this view group continues to intercept touches.
                intercepted = true;
            }

...
//dispatchTransformedTouchEvent方法中
 private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits) {
        final boolean handled;

        // Canceling motions is a special case.  We don't need to perform any transformations
        // or filtering.  The important part is the action, not the contents.
        final int oldAction = event.getAction();
        if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
            event.setAction(MotionEvent.ACTION_CANCEL);
            if (child == null) {
                handled = super.dispatchTouchEvent(event);
            } else {
                handled = child.dispatchTouchEvent(event);
            }
            event.setAction(oldAction);
            return handled;
        }
...
}

}

那View裏的dispatchTouchEvent方法執行的很明顯,直接實現onTouch監聽或執行onTouchEvent方法:

 public boolean dispatchTouchEvent(MotionEvent event) {
        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onTouchEvent(event, 0);
        }

        if (onFilterTouchEventForSecurity(event)) {
            //noinspection SimplifiableIfStatement
            ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) {
                return true;
            }

            if (onTouchEvent(event)) {
                return true;
            }
        }

        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
        }
        return false;
    }

ViewGroup中沒有覆蓋onTouchEvent方法,都是執行View中的onTouchEvent方法。
在View的onTouchEvent方法中,會在ACTION_UP事件中調用performCLick方法,從而響應Button.onClick事件:

public boolean onTouchEvent(MotionEvent event) {
    ...
    switch (event.getAction()) {
                case MotionEvent.ACTION_UP:
                    boolean prepressed = (mPrivateFlags & PREPRESSED) != 0;
                    if ((mPrivateFlags & 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);
                       }

                        if (!mHasPerformedLongPress) {
                            // 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();
                                }
                            }
                        }
                        ....
}

所以這個題目的答案就是,點擊事件從Activity傳遞到ViewGroup,最上層的ViewGroup調用dispatchTouchEvent進行事件分發,接着會調用下面一層ViewGroup的dispatchTouchEvent方法,然後就是Button的dispatchTouchEvent方法,而Button的dispatchTouchEvent方法會調用Button的onTouchEvent方法,在這個方法裏,點擊事件被消費,返回了true,接着Button的dispatchTouchEvent方法也返回true,然後中間ViewGroup的dispatchTouchEvent方法也返回true,然後是最上層的dispatchTouchEvent方法也返回true,最後是Activity的dispatchTouchEvent方法也返回true。事件一步一步向下傳遞,然後一步一步向上冒泡反饋。

參考文章:http://www.cnblogs.com/linjzong/p/4191891.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章