Android觸摸事件傳遞機制

1、觸摸事件類型

觸摸事件對應的類爲MotionEvent類,對應的類型分爲ACTION_DOWN,ACTION_MOVE,ACTION_UP三種,按下觸發ACTION_DOWN,擡起觸發ACTION_UP,如果按下後手指不移動,則不會觸發ACTION_MOVE

2、事件傳遞的三個階段

一次完整的事件傳遞只要包含三個階段:分發(Dispatch)、攔截(Intercept)和消費(Consume)

分別對應的方法爲:dispatchTouchEvent(),onInterceptTouchEvent()和onTouchEvent()

Dispatch :在Android系統中所有的觸摸事件都是通過這個方法分發的,此方法會根據當前視圖的具體邏輯來決定是直接消費事件,還是將事件繼續進行傳遞給子視圖。方法返回true時表示被當前視圖消費掉,不會再繼續分發事件。返回false時要根據具體的情況而定,稍後會有流程圖。返回super.dispatchTouchEvent,表示繼續分發該事件。如果當前的視圖爲ViewGroup或其子類的話,則會調用onInterceptTouchEvent()方法,判斷是否攔截該事件。

Intercept :此方法只在ViewGroup中存在,在View和Activity中不存在。這個方法根據返回的布爾值來決定是否攔截對應的事件。返回true,表示攔截,不在進行分發。返回false或者是super.onInterceptTouchEvent,表示不行攔截繼續傳遞給子視圖。

Consume :此方法返回true時,表示當前的視圖可以處理此事件,事件將不會向上傳遞給父視圖,返回false,表示當前的視圖不會處理此事件,事件將向上傳遞,交於父視圖的onTouchEvent方法處理。

3、View的事件傳遞機制

View控件爲最小控件,不能再放入其他控件,擁有dispathchTouchEvent()和onTouchEvent()方法,爲了清楚的看出View控件的事件傳遞機制,定義一個繼承TextView的MyTextView控件,並進行事件的打印,同時定義一個Activity來展示MyTextView,併爲其設置點擊(onClick)和觸摸(onTouch)的監聽。代碼如下:

MyTextView:

public class MyTextView extends TextView {

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

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

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

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN :
                StringUtils.showLog("MyTextView::dispatchTouchEvent::ACTION_DOWN");
                break;
            case MotionEvent.ACTION_UP :
                StringUtils.showLog("MyTextView::dispatchTouchEvent::ACTION_UP");
                break;
            case MotionEvent.ACTION_MOVE :
                StringUtils.showLog("MyTextView::dispatchTouchEvent::ACTION_MOVE");
                break;
        }
        return super.dispatchTouchEvent(event);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN :
                StringUtils.showLog("MyTextView::onTouchEvent::ACTION_DOWN");
                break;
            case MotionEvent.ACTION_UP :
                StringUtils.showLog("MyTextView::onTouchEvent::ACTION_UP");
                break;
            case MotionEvent.ACTION_MOVE :
                StringUtils.showLog("MyTextView::onTouchEvent::ACTION_MOVE");
                break;
        }
        return super.onTouchEvent(event);
    }
}
TouchEventActivity

public class TouchEventActivity extends AppCompatActivity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_touchevent);
        ButterKnife.bind(this);
    }


    @OnClick({R.id.TouchEvent_click_tv})
    public void touchEventClick(View view) {
        switch (view.getId()) {
            case R.id.TouchEvent_click_tv:
                StringUtils.showLog("MyTextView::onClick");
                break;
        }
    }

    @OnTouch({R.id.TouchEvent_click_tv})
    public boolean touchEventTouch(View view, MotionEvent motionEvent) {
        switch (view.getId()) {
            case R.id.TouchEvent_click_tv:
                switch (motionEvent.getAction()) {
                    case MotionEvent.ACTION_DOWN:
                        StringUtils.showLog("MyTextView::onTouchACTION_DOWN");
                        break;
                    case MotionEvent.ACTION_MOVE:
                        StringUtils.showLog("MyTextView::onTouchACTION_MOVE");
                        break;
                    case MotionEvent.ACTION_UP:
                        StringUtils.showLog("MyTextView::onTouchACTION_UP");
                        break;
                }
                break;
        }
        return super.onTouchEvent(motionEvent);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                StringUtils.showLog("TouchEventActivity::dispatchTouchEvent::ACTION_DOWN");
                break;
            case MotionEvent.ACTION_UP:
                StringUtils.showLog("TouchEventActivity::dispatchTouchEvent::ACTION_UP");
                break;
            case MotionEvent.ACTION_MOVE:
                StringUtils.showLog("TouchEventActivity::dispatchTouchEvent::ACTION_MOVE");
                break;
        }
        return super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                StringUtils.showLog("TouchEventActivity::onTouchEvent::ACTION_DOWN");
                break;
            case MotionEvent.ACTION_UP:
                StringUtils.showLog("TouchEventActivity::onTouchEvent::ACTION_UP");
                break;
            case MotionEvent.ACTION_MOVE:
                StringUtils.showLog("TouchEventActivity::onTouchEvent::ACTION_MOVE");
                break;
        }
        return super.onTouchEvent(event);
    }
}
TouchEventActivity的Xml

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


    <com.fei.mystudy.touchenevt.MyTextView
        android:id="@+id/TouchEvent_click_tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="點擊"/>

</LinearLayout>

運行上面代碼,點擊MyTextView,打印的日誌如下:

04-25 15:43:28.552 29099-29099/com.fei.mystudy I/fei_std: TouchEventActivity::dispatchTouchEvent::ACTION_DOWN
04-25 15:43:28.558 29099-29099/com.fei.mystudy I/fei_std: MyTextView::dispatchTouchEvent::ACTION_DOWN
04-25 15:43:28.558 29099-29099/com.fei.mystudy I/fei_std: MyTextView::onTouchACTION_DOWN
04-25 15:43:28.558 29099-29099/com.fei.mystudy I/fei_std: MyTextView::onTouchEvent::ACTION_DOWN
04-25 15:43:28.647 29099-29099/com.fei.mystudy I/fei_std: TouchEventActivity::dispatchTouchEvent::ACTION_UP
04-25 15:43:28.647 29099-29099/com.fei.mystudy I/fei_std: MyTextView::dispatchTouchEvent::ACTION_UP
04-25 15:43:28.647 29099-29099/com.fei.mystudy I/fei_std: MyTextView::onTouchACTION_UP
04-25 15:43:28.647 29099-29099/com.fei.mystudy I/fei_std: MyTextView::onTouchEvent::ACTION_UP
04-25 15:43:28.648 29099-29099/com.fei.mystudy I/fei_std: MyTextView::onClick

從上面可以看出 dispathTouchEvent()和onTouchEvent()方法的返回值有如下三種情況:

1、返回true;

2、返回false;

3、返回父類的同名方法

不同返回值所造成的的結果不同,歸納總結如下(圖是直接截得圖):



4、ViewGroup的事件傳遞機制

ViewGroup爲View的容器,只要是能放入View控件的控件,都爲ViewGroup或其子類,ViewGroup擁有三個方法,分別是dispathTouchEvent(),onInterceptTouchEvent()以及onTouchEvent(),現定義一個MyLinearLayout,繼承自LinearLayout,加入到上面的測試代碼中。如下:

MyLinearLayout

public class MyLinearLayout extends LinearLayout {
    public MyLinearLayout(Context context) {
        super(context);
    }

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

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


    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN :
                StringUtils.showLog("MyLinearLayout::dispatchTouchEvent::ACTION_DOWN");
                break;
            case MotionEvent.ACTION_UP :
                StringUtils.showLog("MyLinearLayout::dispatchTouchEvent::ACTION_UP");
                break;
            case MotionEvent.ACTION_MOVE :
                StringUtils.showLog("MyLinearLayout::dispatchTouchEvent::ACTION_MOVE");
                break;
        }
        return super.dispatchTouchEvent(event);
//        return false;
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        switch (ev.getAction()){
            case MotionEvent.ACTION_DOWN :
                StringUtils.showLog("MyLinearLayout::onInterceptTouchEvent::ACTION_DOWN");
                break;
            case MotionEvent.ACTION_UP :
                StringUtils.showLog("MyLinearLayout::onInterceptTouchEvent::ACTION_UP");
                break;
            case MotionEvent.ACTION_MOVE :
                StringUtils.showLog("MyLinearLayout::onInterceptTouchEvent::ACTION_MOVE");
                break;
        }
        return super.onInterceptTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN :
                StringUtils.showLog("MyLinearLayout::onTouchEvent::ACTION_DOWN");
                break;
            case MotionEvent.ACTION_UP :
                StringUtils.showLog("MyLinearLayout::onTouchEvent::ACTION_UP");
                break;
            case MotionEvent.ACTION_MOVE :
                StringUtils.showLog("MyLinearLayout::onTouchEvent::ACTION_MOVE");
                break;
        }
        return super.onTouchEvent(event);
//        return true;
    }

}
TouchEventActivity的代碼不變,只是佈局文件要進行更改,如下:

<?xml version="1.0" encoding="utf-8"?>
<com.fei.mystudy.touchenevt.MyLinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:gravity="center"
    android:layout_width="match_parent"
    android:layout_height="match_parent">


    <com.fei.mystudy.touchenevt.MyTextView
        android:id="@+id/TouchEvent_click_tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="點擊"/>

</com.fei.mystudy.touchenevt.MyLinearLayout>

之後運行,打印信息如下:

04-25 16:03:58.022 29099-29099/com.fei.mystudy I/fei_std: TouchEventActivity::dispatchTouchEvent::ACTION_DOWN
04-25 16:03:58.026 29099-29099/com.fei.mystudy I/fei_std: MyLinearLayout::dispatchTouchEvent::ACTION_DOWN
04-25 16:03:58.026 29099-29099/com.fei.mystudy I/fei_std: MyLinearLayout::onInterceptTouchEvent::ACTION_DOWN
04-25 16:03:58.026 29099-29099/com.fei.mystudy I/fei_std: MyTextView::dispatchTouchEvent::ACTION_DOWN
04-25 16:03:58.026 29099-29099/com.fei.mystudy I/fei_std: MyTextView::onTouchACTION_DOWN
04-25 16:03:58.026 29099-29099/com.fei.mystudy I/fei_std: MyTextView::onTouchEvent::ACTION_DOWN
04-25 16:03:58.097 29099-29099/com.fei.mystudy I/fei_std: TouchEventActivity::dispatchTouchEvent::ACTION_UP
04-25 16:03:58.097 29099-29099/com.fei.mystudy I/fei_std: MyLinearLayout::dispatchTouchEvent::ACTION_UP
04-25 16:03:58.097 29099-29099/com.fei.mystudy I/fei_std: MyLinearLayout::onInterceptTouchEvent::ACTION_UP
04-25 16:03:58.097 29099-29099/com.fei.mystudy I/fei_std: MyTextView::dispatchTouchEvent::ACTION_UP
04-25 16:03:58.097 29099-29099/com.fei.mystudy I/fei_std: MyTextView::onTouchACTION_UP
04-25 16:03:58.097 29099-29099/com.fei.mystudy I/fei_std: MyTextView::onTouchEvent::ACTION_UP
04-25 16:03:58.097 29099-29099/com.fei.mystudy I/fei_std: MyTextView::onClick

通過實驗,打印日誌信息得到如下流程圖(直接截取的別人的圖,稍微有點出入,不影響理解):



5、總結

通過上面的代碼和驗證,得到如下的結論:

1、事件傳遞由Activity傳遞到ViewGroup,在由ViewGroup進行遞歸傳遞給它的子View。

2、在子View進行事件的消費後,ViewGroup將收不到事件。

3、ViewGroup在onInterceptTouchEvent()方法中,返回爲true,則會對事件進行攔截,子View不會收到事件,如果返回false或父類方法的時候事件會繼續傳遞給子控件。

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