天天看点

Android 触摸事件传递

近期项目涉及到了大量的手势操作,对Android手势监听也有了更加全面和深入的了解。这里大致的说一下来方便自己和大家理解流程。

一:事件的消费

具体太官方的解释和详情就不多赘述了,从我的个人实际体会来说。会说的比较直接。

首先基本上所有View都是通过一个方法来响应监听事件,

public boolean onTouchEvent(MotionEvent event) ;
           

只要是Android手势操作像一些最常见的OnClickListener、OnClickLongListener等都是通过onTouchEvent封装而来。

这里就会涉及到Android的一个事件传递机制,并不是每个View百分百都能响应监听事件的,像一些如TextView的默认不具有消费点击事件能力的View是默认不能相应onTouch事件的,那系统要怎么知道这个View是否能具有事件消费能力呢。我们重写一个onTouch监听如下:

@Override
    public boolean onTouchEvent(MotionEvent event) {
        return super.onTouchEvent(event);
    }
           

该方法会返回一个boolean类型的返回值,很明显,这个返回值决定了我们是否想要或者说是否消费了该事件。返回true即为消费,返回false反之。

通常最常用的点击事件有三类:Down、Move、Up。

@Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            //当按下
            case MotionEvent.ACTION_DOWN:
                break;
            //当按下并且移动
            case MotionEvent.ACTION_MOVE:
                break;
            //当抬起
            case MotionEvent.ACTION_UP:
                break;
        }
        return super.onTouchEvent(event);
    }
           

通过返回值来判断是否消费会带来一个问题,那就是根据程序执行流程,无论返回值是什么,都会先执行该方法内的其他代码。

那Android是如何实现是否消费的效果呢。

首先一次普通的点击事件肯定会有先后的,第一个响应的一定是Dwon,因此Dwon手势是否消费是无法通过返回值进行判断的(代码执行的先后顺迅),系统则是通过判断Down之后的返回值来判断接下来的手势是否需要消费。

这种判断机制造成了无论返回什么,只要用户点击了屏幕Down手势就一定会被调用。

Down过了之后是Move、最后是Up,所以当返回值判断的是非消费是针对Dwon之后的几个事件来的,如果返回false,将不会收到Move事件,而Up在move之后,更加不能被监听。

也就是说onTouchEvent的返回值决定了该手势事件的下一个事件是否被传递。

这种机制的存在能够方便我们实现更多的组合手势效果,我们可以在判断在一些特定情况下返回特定的值来实现动态效应手势的变化。例如判断move超过一定距离后返回false,将之后的事件交给父容器或者其他View进行处理。

像之前所说的onClickListener是依附于onTouchEvent进行封装的,那到底是不是呢。

我们将onTouchEvent返回值设置成false。同时设置onClick监听,结果onTouch中Down事件可以被相应,但是onClick没有执行。

因为onClick不是在点下的一刻进行回调,而是在松开时进行回调,也就是在Up事件中回调,而我们没有对Up进行消费,所以肯定不会被执行。

如果将onTouchEvent返回值设成true,便能同时相应两个监听。

二:事件的拦截

难道只需要告诉系统我是否需要消费该事件就可以了吗,在实际项目中可能会有各种复杂的手势判断,这样当然是不行的,因此除了View需要有消费能力,还需要父容器传递给相应的事件。

传递的顺序从宏观上大概是:Activity->ViewGroup->View

对应的方法分别是: dispatchTouchEvent() ->onInterceptTouchEvent()->onTouchEvent()

dispatchTochEvent处于事件顶层,依然是通过返回一个boolean类型进行判断,这里就不多介绍

重点需要说明的是onInterceptTouchEvent()这个方法,这是个事件的拦截器,只有ViewGroup才具有拦截功能。

(这点很好理解,根据事件传递顺序,View内不包含别的控件所以也无需传递给更下一层)

当onInterceptTochEvent()返回true代表当前的事件被ViewGroup拦截,该ViewGroup下的子View将不会接收到被拦截的onTouch事件,相应的ViewGroup的onTouchEvent方法则会被回调。

由于ViewGroup向View传递事件是通过onInterceptTochEvent()方法,当返回ture回调ViewGroup自身的onTouchEvent,当返回fasle回调子View的onTouchEvent,所以如果拦截,子View会连Down事件也无法收到。

而之前那种是在收到事件后,选择是否消费时,即使不消费依然能获得Down事件。但是在这里如果事件被父View拦截,就会连事件都无法收到,自然不能消费。

如果ViewGroup没有拦截该事件,会传给他的子View进行消费,也就是子View的onTouchEvent,如果返回true,即被该子View消费,不会再传给下一个View,并且ViewGroup中的onTouchEvent也不会被调用。反之如果返回false则代表该ViewGroup中的子View不具备消费事件的能力,将有ViewGroup进行自行处理,OnTochEvent方法将被回调。

还有一点比较特殊,就是当ViewGroup对事件是否进行拦截对onInterceptTouchEvent()这个方法本身是没有影响的,也就是说不管ViewGroup是否拦截了事件,onInterceptTouchEvent方法中都能获得相关的点击事件。

(简单一句话阐述就是,如果父布局拦截就不管子布局,自己消费,如果不拦截就依次询问自己的子布局是否想要消费,如果所有子布局都不想消费,最终就由父布局来选择进行消费。并且无论是否拦截,是否消费,只要父容器得到了点击事件,父布局中的onInterceptTouchEvent()都会被回调。)

总结出来,当系统收到触摸事件的处理流程如下:

首先绘发给Acitivity通过dispatchTochEvent进行下发,正常情况会发给当前区域的ViewGroup,再由Viewgroup依次下发给子View。ViewGroup有权利进行事件的拦截和消费,或者下发给自己的子View。

根据测试ViewGroup在下发事件的时候会先发给上层的View,也就是addView中最后的一个,如果把Viewgroup看成一个栈,每次传递顺序是从栈顶往下传发。

虽然已经尽量说的清晰,结果写出来还是比较乱,第一次写这么长的文章,如果对你有帮助,请点赞或者评论。