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

來源:互聯網
上載者:User

標籤: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);    }


布局檔案如下:

<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"    android:paddingBottom="@dimen/activity_vertical_margin"    android:paddingLeft="@dimen/activity_horizontal_margin"    android:paddingRight="@dimen/activity_horizontal_margin"    android:paddingTop="@dimen/activity_vertical_margin"    tools:context=".MainActivity">    <com.xjp.testtouchevent.MyButton        android:id="@+id/myButton"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:text="測試" /></RelativeLayout>


測試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介面回調。








 

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

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.