Android 学习笔记:View的事件分发机制 解析及实例讲解

想必大家对编写自定义控件的流程不陌生,独自编写过许多继承ViewViewGroup之类的自定义控件。在编写的过程中肯定要考虑到View的事件分发机制,不可避免的重要部分,各位有考虑过以下问题:

1. 事件的传递机制?
2. 事件的分发过程涉及到的方法?
3. 接收事件的方法的优先级?

接下来的内容依次来解析:
(以下融合个人理解和任玉刚老师的《Android开发艺术探索》书中内容)




点击事件的传递规则

1.方法介绍

以下分析的主要对象就是 点击事件(MotionEvent) 。而点击事件的事件分发,其实就是对 MotionEvent 事件的分发过程。当屏幕上的点击事件产生后,Android系统会将此事件传递一个具体的View去响应,而这个传递的过程就是分发过程

接下来介绍的三种方法就是来完成点击事件的分发过程:

1). dispatchTouchEvent

public boolean dispatchTouchEvent(MotionEvent ev)

用来进行事件的分发。如果点击事件能够传递到当前View,那此方法一定会被调用。

返回结果受当前 ViewonTouchEvent 和下级ViewdispatchTouchEvent方法的影响(若下级View指定消费点击事件,则它的返回结果是false),表示是否消耗当前事件。


2). onIterceptTouchEvent

public boolean onInterceptTouchEvent(MotionEvent event)

在上述方法内部调用,用于判断是否拦截点击事件。若当前View拦截了点击事件,在同一个事件序列中,此方法不会再次被调用。返回结果表示是否拦截当前点击事件。


3). onTouchEvent

public boolean onTouchEvent(MotionEvent event)

也是在 dispatchTouchEvent 方法中调用,用于处理具体点击事件(最常用的三个:DOWNUPMOVE)。返回结果表示是否消耗当前事件,如果不消耗,则在同一个事件序列中,当前无法再次接收到事件!

【举个栗子:】
比如说在onTouchEvent 方法中分别监视了DOWNUPMOVE三种状态,并有对应的逻辑处理,若不将返回结果改为true,则手指在屏幕上操作时,只会响应 DOWN 的逻辑,后面的则忽略掉!

【未修改返回值:】
这里写图片描述

【修改返回值true:】
这里写图片描述

以上GIF动图明显对比出不同了,这是一个自定义粘性控件,我在onTouchEvent 方法中分别监视了DOWNUPMOVE三种状态,并有对应的逻辑处理。而第一个GIF动图中方法返回值未修改:
return super.onTouchEvent(event);
所以如上所说,由于返回值为修改,它检测到DOWN事件后,做出响应,而后的MOVEUP事件都无法检测,显示出来的效果是卡顿的感觉。

而第二个GIF动图中设置了返回值:
return true;
表示消耗当前事件,所以在同一个事件序列中,可以再次接收到事件!而这才是我们需要的结果。

由此可见返回值的重要性,接下来继续深入了解这三个方法:




2. 三个方法之间的联系

其实上述三个方法的区别与联系可用以下伪代码表示:

public boolean dispatchTouchEvent(MotionEvent ev){
    boolean consume = false;
    if(onInterceptTouchEvent(ev)){
        consume = onTouchEvent(ev);
    }else {
        consume = child.dispatchTouchEvent(ev);
    }

    return consume;
}

通过以上伪代码,可得知点击事件的传递规则:当一个点击事件产生后,一个根ViewGroup会最先接收!这时它的 dispatchTouchEvent 方法会被调用:

1). 若此方法返回值为true,代表它要拦截此事件,所以此点击事件会交给此 ViewGroup 处理,即它的onTouchevent 方法随之被调用;

2). 若此方法返回值为false,代表它不拦截此事件,这时当前事件被继续传递给它的子元素,接着子ViewdispatchTouchEvent 方法会被调用,再根据返回值做出选择,如此反复直到此点击事件被最终处理!

这里写图片描述




3. OnTouchListener 和 onTouchEvent 和 OnClickListener 优先级

//OnTouchListener 

        view.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View view, MotionEvent motionEvent) {
                return false;
            }
        });
//onTouchEvent

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        return super.onTouchEvent(event);
    }
//OnClickListener
        view.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {

            }
        });

当一个View 需要处理事件时,如果它设置了 OnTouchListener ,则 OnTouchListener 中的onTouch 方法会被回调。这时事件如何处理取决于onTouch 方法的返回值:

1)若返回false,则当前ViewonTouchEvent 方法会被调用。
2)若返true,此点击事件已被OnTouchListener 中的onTouch 方法消费掉, onTouchEvent 方法不会被调用!

可得出部分结论:View设置的OnTouchListener ,其优先级比onTouchEvent 高!

而在onTouchEvent 方法中,如果View 还设置了 OnClickListener,这时它的onClick 方法才会被调用!

最终结论:
优先级排序:OnTouchListener >onTouchEvent >OnClickListener




4. 点击事件的传递顺序(Activity 、Window 、View )

当一个点击事件产生后,传递顺序为: Activity —> Window –> View即事件总是先传递给Activity ,再传递给Window,最后Window传递给顶级View。而顶级View 接收到此点击事件,就会按照事件分发机制(即上面的逻辑图)。

考虑一个特殊情况:如果一个子ViewonTouchEvent 返回false,那么它的父容器的 onTouchEvent 会被调用,依次类推。若所有的元素不处理此事件,那么此点击事件最终传递给Activity处理,因此ActivityonTouchEvent 方法会被调用!

举一个通俗易懂的栗子:这个点击事件就是一个苹果,而Activity就是爷爷,依次传递的是爸爸和儿子。现在爷爷收到一个苹果,心疼上班辛苦的爸爸,给他吃,而爸爸心疼自己的小儿子,给儿子吃。儿子拿到了苹果,嫌弃苹果不新鲜,又还给了爸爸(onTouchEvent返回false),爸爸一看是有些不新鲜,还给了爷爷,爷爷舍不得丢,自己处理掉了(上级的onTouchEvent被调用)。这就是一个传递过程(领导与员工的例子也很贴切)






事件传递机制的一些结论及注意

1. 事件序列

(1). 定义:同一事件序列是从手指接触屏幕的那一刻起,到手指离开屏幕的那一刻结束。Touch时最明显的三个状态,事件序列从DOWN 事件开始,手指在屏幕上滑动即MOVE事件,最后以手指抬起 UP事件结束掉这个事件序列。

(2)注意:某个View一旦拦截此点击事件,那么这个事件序列都会由它来处理,并且它的onInterceptTouchEvent不会被调用!也证实了一开始所讲dispatchTouchEvent方法中返回true后,无需再调用此ViewonInterceptTouchEvent来确定是否需要拦截。

(3)注意: 某个View一旦开始处理事件,若它消耗ACTION_DOWN 事件(即onTouchEvent返回了 false),那么同一事件序列的其它事件都不会交给它来处理,并交由父元素处理!

正常情况下,一个事件序列只能被一个View拦截且消耗。


2. ViewGroup

ViewGroup默认不拦截任何事件。Android源码中ViewGrouponInterceptTouchEvent 方法默认返回值为false.


3.View

(1) View 没有 onInterceptTouchEvent 方法,一旦有点击事件传递给它,它的onTouchEvent方法会被调用。

(2) ViewonTouchEvent默认都会消耗事件(返回true),除非它是不可点击的(clickablelongClickable 同时为false)。ViewlongClickable 属性默认值为falseclickable属性要分情况:比如Buttonclickable 属性默认为true,而TextViewclickable属性默认为false

(3) Viewenable 属性不影响 onTouchevent 的默认返回值。哪怕一个Viewdisable状态的,只要它的clickable或者 longClickable属性默认为true,而TextViewclickable属性默认为false.

(4) onClick 会发生的前提是当前View是可点击的,并且它收到了DOWNUP的事件。


4 .总结

事件传递过程是由外向内的,即事件总是先传递给父元素,然后再由父元素分发给子View,通过 requestDisallowInterceptTouchEvent 方法可以在子元素中敢于父元素的事件分发过程,ACTION_DOWN事件除外。





实例证明

1. 自定义控件(ViewGroup)

这里写图片描述

如上图所示,这里是一个彷QQ侧滑栏的自定义控件(ViewGroup)。很明显,操作过程中产生了点击事件:右滑显示侧边栏,点击可直接关闭侧边栏。

这里需要注意的是两个不同的子View(主界面和侧滑栏)里还有下级ViewlistView),所以当点击事件传递到自定义控件SlideMenu这个View的时候,必须拦截下来,消费掉此点击事件!

所以我们需要重写onInterceptTouchEvent 方法和onTouchEvent 方法,并且修改两个方法的返回值!

    //3.触摸事件传递及拦截事件
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        return  viewDragHelper.shouldInterceptTouchEvent(ev);

    }
   //返回true,代表消费此事件,自己来处理
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        viewDragHelper.processTouchEvent(event);
        return true;
    }

由以上代码可知,这里重写并修改了返回值,onTouchEvent 方法中返回值为true,代表消耗此点击事件!也许你有个疑问:这里重写了两个方法,那第一个方法dispatchTouchEvent

正因如此,证实了在介绍dispatchTouchEvent 方法时说的:返回结果受当前 ViewonTouchEvent 和下级ViewdispatchTouchEvent方法的影响!所以重写的两个方法中已经决定此方法的返回值。



2. 自定义控件(View)

这里写图片描述

如上图所示,这是个粘性自定义控件(View),对屏幕的操作产生了一系列的事件序列,所以此控件需要消费掉此点击事件!

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            ......
            break;
        case MotionEvent.ACTION_MOVE:
            ......
            break;
        case MotionEvent.ACTION_UP:
            ......
            break;
        }
        invalidate();
        return true;

    }

以上代码所示,这里的事件序列是从 DOWN事件开始产生的,持续MOVE事件,最后以手指抬起UP事件结束!所以重写onTouchEvent 方法,并且修改返回值为true

同时也证实了上述结论中的一点:
View 没有 onInterceptTouchEvent 方法,一旦有点击事件传递给它,它的onTouchEvent方法会被调用。






以上大部分都是摘于任玉刚老师的《Android开发艺术探索》,非常好的一本书!详细介绍了View部分,而这边博客也是基于“View的事件分发机制”这一点进行讲解,考虑到纯理论部分有些枯燥,加了一些例子讲解,后面再写一篇源码分析的,也是书中的内容,推荐大家~

希望对你们有帮助:)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值