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或父类方法的时候事件会继续传递给子控件。

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