Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> 從源碼角度帶你分析 Android View 事件分發 dispatchTouchEvent,onTouch,onTouchEvent,onClick邏輯順序過程(一)

從源碼角度帶你分析 Android View 事件分發 dispatchTouchEvent,onTouch,onTouchEvent,onClick邏輯順序過程(一)

編輯:關於Android編程

關於Android View 事件分發過程的文章網絡上可以搜到一把大,這裡貼一篇代碼性的文章,作者也是個牛人:Android事件分發機制完全解析,帶你從源碼的角度徹底理解(上)。

雖然講的很好,但是看完之後還是感覺有那麼點一知半解,於是自己花了點時間從源碼研究android 觸摸事件分發流程,以下內容僅僅個人理解,如有差錯希望指出。

 

我們先從一個例子看起,先重寫一個MyButton 繼承Button,代碼如下:

 

public class MyButton extends Button {

    public MyButton(Context context) {
        super(context);
    }

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

    public MyButton(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }


    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {

        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                MyLog.e("dispatchTouchEvent====MyButton=====ACTION_DOWN");
                break;
            case MotionEvent.ACTION_MOVE:
                MyLog.e("dispatchTouchEvent====MyButton=====ACTION_MOVE");
                break;
            case MotionEvent.ACTION_UP:
                MyLog.e("dispatchTouchEvent====MyButton=====ACTION_UP");
                break;
        }

        return super.dispatchTouchEvent(event);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {

        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                MyLog.e("onTouchEvent====MyButton=====ACTION_DOWN");
                break;
            case MotionEvent.ACTION_MOVE:
                MyLog.e("onTouchEvent====MyButton=====ACTION_MOVE");
                break;
            case MotionEvent.ACTION_UP:
                MyLog.e("onTouchEvent====MyButton=====ACTION_UP");
                break;
        }

        return super.onTouchEvent(event);
    }

 

 

布局文件如下:

 



    



測試Activity如下:

 

 

public class MainActivity extends ActionBarActivity {

    private Button myButton;


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

        myButton = (Button) findViewById(R.id.myButton);

        myButton.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                switch (event.getAction()) {
                    case MotionEvent.ACTION_DOWN:
                        MyLog.e("onTouch====MyButton=====ACTION_DOWN");
                        break;
                    case MotionEvent.ACTION_MOVE:
                        MyLog.e("onTouch====MyButton=====ACTION_MOVE");
                        break;
                    case MotionEvent.ACTION_UP:
                        MyLog.e("onTouch====MyButton=====ACTION_UP");
                        break;
                }
                return false;
            }
        });

        myButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                MyLog.e("onClick====MyButton=====onClick");
            }
        });


    }


}
\
點擊測試按鈕,打印結果如下:

 

\

我們從打印結果可以直觀看到,點擊Button按鈕事件分發過程如下 dispatchTouchEvent---->onTouch---->onTouchEvent----->onClick。並且如果仔細的你會發現,都是在ACTION_UP事件之後才觸發onClick點擊事件,為什麼會是這樣??現在我們不得而知。我們僅僅是從打印結果推測事件分發的結論,現在我們從源碼分析下這個事件分發流程為什麼是這樣子。

 

事件分發都是從dispatchTouchEvent方法開始的,那麼我們這裡是重寫了dispatchTouchEvent方法,並且最後也調用了父類的super.dispatchTouchEvent(event)方法。那麼我們看看父類中的方法到底做了什麼??點擊進入父類的dispatchTouchEvent方法,發現此方法在View類中找到,其實也不奇怪,所有控件的父類都是View。這裡我貼出最新源碼如下:

 

 

    public boolean dispatchTouchEvent(MotionEvent event) {
        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();
        }

        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;
    }


 

忽略其他無關代碼,我們直接看17--25行。第17行的if判斷關鍵在於li.mOnTouchListener.onTouch(this, event) 的返回值,這個接口回調就是我們外面寫的myButton.setOnTouchListener事件(Button 的onTouch事件),在MainActivity代碼裡,我們setOnTouchListener返回的值是false,所以在源碼中我們可以看到 17行的條件不成立,那麼條件不成立,result=false;因此,源碼的第23行if 判斷第一個條件成立,繼續執行第二個條件,也就是onTouchEvent。我們跳到這個方法裡看看裡面干啥了?看如下代碼:

 

 

public boolean onTouchEvent(MotionEvent event) {

        if (((viewFlags & CLICKABLE) == CLICKABLE ||
                (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
            switch (event.getAction()) {
                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) {
                            // 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();
                    }
                    break;
            return true;
        }

        return false;
    }

我們看看這裡邊都做了些什麼,忽略其他,我們直接看37行的 performClick(); 方法,跳進去繼續看,(注意:這裡的performClick方法是在ACTION_UP手勢裡邊執行的哦!!!)

 

 

 

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;
    }
看見沒??第6行 li.mOnClickListener.onClick(this); 這個接口回調就是我們Button的 onClick事件。到此為止,我們從源碼分析了Button事件分發過程
結論:dispatchTouchEvent---->onTouch---->onTouchEvent----->onClick。並且如果仔細的你會發現,是在所有ACTION_UP事件之後才觸發onClick點擊事件。

 

現在我們來看看其他情況:當onTouch返回為true,打印結果如下:

\

驚奇的發現,竟然沒有執行onClick事件是吧????如果你仔細閱讀上面的文章,估計你知道為什麼了吧?還是跟大家一起分析一下吧:源碼如下:

 

 

 public boolean dispatchTouchEvent(MotionEvent event) {
        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();
        }

        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;
    }

從第17行可以看出,條件成立,result=true;那麼第23行if條件根本不會執行第二個判斷,那麼就不會執行onTouchEvent方法,也就不會調用 onClick的接口,因此Button 不會執行setOnClickListener中的onClick事件。

 

 

給個簡單的流程圖如下

\

 

 

因此,事件分發之間的關系是:dispatchTouchEvent方法中線執行 onTouch接口回調,然後根據onTouch方法的返回值判斷是否執行onTouchEvent方法,onTouchEvent方法中執行了onClick接口回調。

 

 

 

 

 

 

 

 

 

 

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved