频道栏目
首页 > 资讯 > 其他 > 正文

TouchEvent事件分发机制详解

17-06-19        来源:[db:作者]  
收藏   我要投稿

TouchEvent事件分发机制算作是Android开发中很重要的知识点了,以前一直对这个传递过程有点模糊,现在来仔细研究下这整个过程

一、概念解释

在Android系统中,View 类是所有控件和容器的直接父类或间接父类。而在本文中,View 这个单词是指最小单位的控件View,不再包含其他子控件,例如 TextView 、Button等。ViewGroup 则是指可以包含View的容器,例如 LinearLayout、FrameLayout等。

触摸事件对应的是 MotionEvent 类,事件的类型主要有如下三种:

Action_Down :用户手指的按下操作,标志着一次触摸事件的开始 Action_Move:用户手指按压屏幕后,在松开手指之前如果移动距离超出一定的阈值,则发生了Action _ Move 事件 Action_Up:用户手指离开屏幕时触发的操作,标志着当前触摸事件的结束

在一次屏幕触摸操作中,Action_Down和Action _ Up这两个事件是必需的,Action _ Move 事件则视情况而定

二、事件传递的三个阶段

一次完整的事件传递包括三个阶段,分别是事件的发布、拦截和消费。发生事件传递的视图可以分为三类:Activity、View和ViewGroup

2.1、发布(Dispatch):

事件的发布对应着如下方法:

public boolean dispatchTouchEvent(MotionEvent ev)

在Android系统中,所有的触摸事件都是通过这个方法来发布的。在这个方法中,根据当前视图的具体实现逻辑,来决定是直接消费这个事件还是将事件继续发布给子视图处理。
返回true表示事件被当前视图消费掉,不再继续发布事件。返回false则依据视图类型会有所不同。返回 super.dispatchTouchEvent(ev) 表示继续发布该事件。如果当前视图是 ViewGroup 及其子类,则会调用 onInterceptTouchEvent(MotionEvent ev) 方法判定是否拦截该事件

2.2、拦截(Intercept):

事件的拦截对应着如下方法:

public boolean onInterceptTouchEvent(MotionEvent ev)

这个方法只在 ViewGroup 及其子类中才有,在View和Activity中是不存在的
该方法通过返回值来决定是否拦截对应的事件。返回true表示拦截这个事件,不继续发布给子视图,同时交由自身的 onTouchEvent(MotionEvent event) 方法进行处理;返回false或者 super.onInterceptTouchEvent(ev) 表示不对事件进行拦截,继续传递给子视图

2.3、消费:

事件的消费对应着如下方法:

public boolean onTouchEvent(MotionEvent event)

该方法返回true表示当前视图可以处理对应的事件,事件将不会传递给父视图;返回false表示当前视图不处理这个事件,事件会被传递给父视图的相同方法进行处理

三、View 的事件传递流程

首先继承 AppCompatTextView 类并重写其与TouchEvent事件发布相关的两个方法,输出相应的触摸事件类型

/**
 * Created by CZY on 2017/6/7.
 */
public class MyTextView extends AppCompatTextView {

    private final String TAG = "MyTextView";

    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 ev) {
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                Log.e(TAG, "dispatchTouchEvent  ACTION_DOWN");
                break;
            case MotionEvent.ACTION_MOVE:
                Log.e(TAG, "dispatchTouchEvent  ACTION_MOVE");
                break;
            case MotionEvent.ACTION_UP:
                Log.e(TAG, "dispatchTouchEvent  ACTION_UP");
                break;
        }
        return super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                Log.e(TAG, "onTouchEvent  ACTION_DOWN");
                break;
            case MotionEvent.ACTION_MOVE:
                Log.e(TAG, "onTouchEvent  ACTION_MOVE");
                break;
            case MotionEvent.ACTION_UP:
                Log.e(TAG, "onTouchEvent  ACTION_UP");
                break;
        }
        return super.onTouchEvent(event);
    }

}

在布局文件中声明使用MyTextView




    


重写 MainActivity 中与触摸事件相关的两个方法,输出相应的触摸事件类型,并为 MyTextView 设置 TouchEvent 事件监听

public class MainActivity extends AppCompatActivity implements View.OnTouchListener{

    private final String TAG = "Activity";

    private final String TAG_VIEW = "MyTextView";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        findViewById(R.id.myTextView).setOnTouchListener(this);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                Log.e(TAG, "dispatchTouchEvent  ACTION_DOWN");
                break;
            case MotionEvent.ACTION_MOVE:
                Log.e(TAG, "dispatchTouchEvent  ACTION_MOVE");
                break;
            case MotionEvent.ACTION_UP:
                Log.e(TAG, "dispatchTouchEvent  ACTION_UP");
                break;
        }
        return super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                Log.e(TAG, "onTouchEvent  ACTION_DOWN");
                break;
            case MotionEvent.ACTION_MOVE:
                Log.e(TAG, "onTouchEvent  ACTION_MOVE");
                break;
            case MotionEvent.ACTION_UP:
                Log.e(TAG, "onTouchEvent  ACTION_UP");
                break;
        }
        return super.onTouchEvent(event);
    }

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        switch (v.getId()) {
            case R.id.myTextView:
                switch (event.getAction()) {
                    case MotionEvent.ACTION_DOWN:
                        Log.e(TAG_VIEW, "onTouch  ACTION_DOWN");
                        break;
                    case MotionEvent.ACTION_MOVE:
                        Log.e(TAG_VIEW, "onTouch  ACTION_MOVE");
                        break;
                    case MotionEvent.ACTION_UP:
                        Log.e(TAG_VIEW, "onTouch  ACTION_UP");
                        break;
                }
                break;
        }
        return false;
    }

}

此时,运行程序后点击 MyTextView 控件,输出的Log如下所示:

06-13 12:13:26.384 9522-9522/com.czy.touchevent E/Activity: dispatchTouchEvent    ACTION_DOWN
06-13 12:13:26.384 9522-9522/com.czy.touchevent E/MyTextView: dispatchTouchEvent  ACTION_DOWN
06-13 12:13:26.385 9522-9522/com.czy.touchevent E/MyTextView: onTouch             ACTION_DOWN
06-13 12:13:26.385 9522-9522/com.czy.touchevent E/MyTextView: onTouchEvent        ACTION_DOWN
06-13 12:13:26.385 9522-9522/com.czy.touchevent E/Activity: onTouchEvent          ACTION_DOWN
06-13 12:13:26.480 9522-9522/com.czy.touchevent E/Activity: dispatchTouchEvent    ACTION_UP
06-13 12:13:26.480 9522-9522/com.czy.touchevent E/Activity: onTouchEvent          ACTION_UP

在默认情况下,Activity 与 MyTextView 的各个触摸事件相关方法的调用顺序如上所示。可以看到,MyTextView 的 onTouch 方法比 onTouchEvent 方法更早被调用,说明 onTouch 方法的优先级更高。

dispatchTouchEvent 方法和 onTouchEvent 方法的返回值存在三种情况:

返回 true 返回 false 返回 父类的同名方法

通过不断改变 Activity 与 MyTextView 中各个方法的返回值,可以得到如下所示的TouchEvent事件发布机制流程图:
这里写图片描述

从上面的流程图可以得出以下结论:

触摸事件的传递流程是从 dispatchTouchEvent 方法开始的,如果默认返回父类的同名函数,则事件将会依照嵌套层次从外层向内层传递,到达最内层的View时,就由它的 onTouchEvent 方法进行处理。该方法返回 true 则表示消费了该事件;如果处理不了则返回 false,这时事件会重新向外层传递,并交由外层的View、ViewGroup或者Activity的 onTouchEvent 方法进行处理,依次类推。 如果事件传递在向内层传递过程中由于人为干预,事件处理函数返回 true ,则会导致事件被提前消费掉,内层 View 或ViewGroup将不会收到这个事件 View控件的事件触发顺序是先执行 onTouch 方法,再执行 onTouchEvent 方法,onClick 方法排在最后。如果优先级高的方法返回了 true,则事件将不会继续传递

四、ViewGroup 的事件传递流程

ViewGroup 相比 View和Activity多出了一个 onInterceptTouchEvent(MotionEvent ev) 方法
首先继承 LinearLayout 类并重写其与TouchEvent事件发布相关的三个方法,输出相应的触摸事件类型

/**
 * Created by CZY on 2017/6/7.
 */
public class MyLinearLayout extends LinearLayout {

    private final String TAG = "外层ViewGroup";

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

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

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

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                Log.e(TAG, "dispatchTouchEvent  ACTION_DOWN");
                break;
            case MotionEvent.ACTION_MOVE:
                Log.e(TAG, "dispatchTouchEvent  ACTION_MOVE");
                break;
            case MotionEvent.ACTION_UP:
                Log.e(TAG, "dispatchTouchEvent  ACTION_UP");
                break;
        }
        return super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                Log.e(TAG, "onInterceptTouchEvent   ACTION_DOWN");
                break;
            case MotionEvent.ACTION_MOVE:
                Log.e(TAG, "onInterceptTouchEvent   ACTION_MOVE");
                break;
            case MotionEvent.ACTION_UP:
                Log.e(TAG, "onInterceptTouchEvent   ACTION_UP");
                break;
        }
        return super.onInterceptTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                Log.e(TAG, "onTouchEvent  ACTION_DOWN");
                break;
            case MotionEvent.ACTION_MOVE:
                Log.e(TAG, "onTouchEvent  ACTION_MOVE");
                break;
            case MotionEvent.ACTION_UP:
                Log.e(TAG, "onTouchEvent  ACTION_UP");
                break;
        }
        return super.onTouchEvent(event);
    }

}

Activity 的布局文件代码如下所示:




    

运行程序后点击 MyTextView 控件,输出的Log如下所示:

06-18 04:22:54.669 12309-12309/com.czy.touchevent E/Activity: dispatchTouchEvent  ACTION_DOWN
06-18 04:22:54.669 12309-12309/com.czy.touchevent E/外层ViewGroup: dispatchTouchEvent  ACTION_DOWN
06-18 04:22:54.669 12309-12309/com.czy.touchevent E/外层ViewGroup: onInterceptTouchEvent   ACTION_DOWN
06-18 04:22:54.669 12309-12309/com.czy.touchevent E/MyTextView: dispatchTouchEvent  ACTION_DOWN
06-18 04:22:54.669 12309-12309/com.czy.touchevent E/MyTextView: onTouch  ACTION_DOWN
06-18 04:22:54.669 12309-12309/com.czy.touchevent E/MyTextView: onTouchEvent  ACTION_DOWN
06-18 04:22:54.669 12309-12309/com.czy.touchevent E/外层ViewGroup: onTouchEvent  ACTION_DOWN
06-18 04:22:54.669 12309-12309/com.czy.touchevent E/Activity: onTouchEvent  ACTION_DOWN
06-18 04:22:54.764 12309-12309/com.czy.touchevent E/Activity: dispatchTouchEvent  ACTION_UP
06-18 04:22:54.764 12309-12309/com.czy.touchevent E/Activity: onTouchEvent  ACTION_UP

在默认情况下,Activity 、ViewGroup和 View 的各个触摸事件相关方法的调用顺序如上所示
通过不断改变 Activity、ViewGroup 和 View 中各个方法的返回值,可以得到如下所示的TouchEvent事件发布机制流程图:

这里写图片描述

从上面的流程图可以得出以下结论:

ViewGroup 通过 onInterceptTouchEvent 方法对事件进行拦截。如果该方法返回true,则事件不会继续传递给子View;如果返回false或 super.onInterceptTouchEvent ,则事件会继续传递给子View 在子View中对事件进行消费后,ViewGroup将接收不到任何事件

五、事件传递的“记忆”功能

从上边展示的事件传递默认的方法调用顺序可以看出来,Action_Up事件都是直接交由 Activity 进行处理,而没有传递给内部的ViewGroup或View。
其实,dispatchTouchEvent 方法具有“记忆”功能。如果 Action_Down 事件传递给了某 ViewGroup(或者Activity),ViewGroup 默认继续向下传递交由子View进行处理,ViewGroup 会记录该事件是否被子View给消费了。那 ViewGroup 如何知道子View是否消费了该事件呢?如果该事件会再次向上传递给 ViewGroup 的onTouchEvent 方法进行处理,那就说明子View没能消费掉该事件。当第二次事件(Action _ Move或者Action _ Up)向下传递到该 ViewGroup, ViewGroup 的 dispatchTouchEvent 方法会进行判断,若子View消费了上次的 Action _ Down 事件,那么本次事件就继续向下传递交由子View进行处理,若上次的事件没有被子View所消费,那么本次的事件就不会继续向下传递了,ViewGroup 直接调用自己的 onTouchEvent 方法来处理该事件。
“记忆”的有效期只在单次的触摸事件中,即从Action _ Down 事件开始,在 Action _ Up 事件结束。

相关TAG标签
上一篇:android studio遇到的一些问题
下一篇:Maven(打包Scala程序打不进class文件和依赖的解决办法)
相关文章
图文推荐

关于我们 | 联系我们 | 广告服务 | 投资合作 | 版权申明 | 在线帮助 | 网站地图 | 作品发布 | Vip技术培训 | 举报中心

版权所有: 红黑联盟--致力于做实用的IT技术学习网站