Android 事件拦截和分发机制分析

当android系统捕获到用户的各种输入事件后,android提供了一整套完整的事件传递、处理机制,来帮助开发者完成准确的事件分配与处理。触摸事件就是捕获屏幕后的产生的事件。

Android为触摸事件封装了一个类—MotionEvent。
MotionEvent典型的事件类型有以下几种:
ACTION_DOWN—手指刚接触屏幕;
ACTION_UP—手指从屏幕上松开的一瞬间;
ACTION_MOVE—手指在屏幕上移动;
MotionEvent封装了触摸点的坐标,可以通过event.getX()方法和event.getRawX()方法取出坐标点。

Android的View结构是树形结构,也就是说View可以放在ViewGroup里面,通过不同的组合来实现不同的样式。那么问题来了,View放在一个ViewGroup里面,这个ViewGroup又放在另一个ViewGroup里面,甚至可能连续嵌套,一层层叠起来。但是,触摸事件就一个,同一个事件,子View和父ViewGroup都有可能想要进行处理,因此产生了“事件拦截“。

举例说明:假设你所在的公司,有一个总经理,级别最高,他下面又个总经理,级别次之,最底层,就是干活的你,没有级别。现在董事会交给总经理一个任务,总经理将这个任务布置给部长,部长将这个任务安排给了你。当你干完活了,你就把任务交给部长,部长觉得任务完成不错,于是签了名字交给总经理,总经理也觉得不错,就也签了名字交给董事会。这样就一个任务完成了。

——总经理——MyViewGroupA,最外层的ViewGroup
——部长——MyViewGroupB,中间层的ViewGroup
——你——MyView,最底层

对于viewgroup,重写如下三种方法:

/**
     * 用来进行事件分发。如果事件能够传递给当前View,那么此方法一定被调用,
     * 返回结果受当前View的onTouchEvent和下级View的dispatchTouchEvent方法影响,
     * 表示是否消耗当前事件
     */
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        Log.d("chunsoft","ViewGroupA dispatchTouchEvent"+ev.getAction());
        return super.dispatchTouchEvent(ev);
    }

    /**
     * 在上述方法内部调用,用力啊判断是否拦截某个事件,返回结果表示是否消耗当前事件,
     * 如果不消耗,则在同一个事件序列中,此方法不会再次被调用,返回结果表示是否拦截当前事件
     */
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        Log.d("chunsoft","ViewGroupA onInterceptTouchEvent"+ev.getAction());
        return super.onInterceptTouchEvent(ev);
    }

    /**
     * 在dispatchTouchEvent方法中被调用,用来处理点击事件,返回结果表示是否消耗当前事件,
     * 如果不消耗,在同一事件序列当中,当前View无法再次接收到事件
     */
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.d("chunsoft","ViewGroupA onTouchEvent"+event.getAction());
        return super.onTouchEvent(event);
    }

对于View来说,重写如下所示的两个方法:

@Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.d("chunsoft","View onTouchEvent" + event.getAction());
        return super.onTouchEvent(event);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        Log.d("chunsoft","View dispatchTouchEvent" + event.getAction());
        return super.dispatchTouchEvent(event);
    }

从上面代码可以看出,ViewGroup级别较高比View多了一个方法—onInterceptTouchEvent(),从名字可以看出这是事件拦截的核心方法,点击下,view,看log会怎样记录我们的操作和程序的响应。如下图,最小的正方形View是自定义View,而其他两个View都是自定义的ViewGroup。
这里写图片描述
(1)正常点击MyView
点击View后的Log如下所示:

chunsoft: ViewGroupA dispatchTouchEvent
chunsoft: ViewGroupA ViewGroupA onInterceptTouchEvent
chunsoft: ViewGroupB dispatchTouchEvent
chunsoft: ViewGroupB onInterceptTouchEvent
chunsoft: View dispatchTouchEvent
chunsoft: View onTouchEvent
chunsoft: ViewGroupB onTouchEvent
chunsoft: ViewGroupA onTouchEvent

事件传递顺序:
总经理(ViewGroupA)—>部长(ViewGroupB)—>你(View)。事件传递的时候先执行dispatchTouchEvent()方法,再执行onInterceptTouchEvent()方法。

事件传递的返回值,True,拦截,不继续;False,不拦截,继续;默认false;

事件处理顺序:
你(View)—>部长(ViewGroupB)—>总经理(ViewGroupA)

事件处理的返回值:True,处理了,不用审核;False,给上级处理。初始值false

在事件传递中,虽然dispatchTouchEvent()是事件分发的第一步,但是一般情况不会改写,onInterceptTouchEvent()是主要关注的。
(2)总经理(MyViewGroupA)拦截事件传递
假设总经理(MyViewGroupA)发现这个任务太简单,自己完成。因此事件被总经理(MyViewGroupA)使用onInterceptTouchEvent()方法把事件拦截下来,即让MyViewGroupA的onInterceptTouchEvent()方法返回true,log日志如下:

chunsoft: ViewGroupA dispatchTouchEvent
chunsoft: ViewGroupA ViewGroupA onInterceptTouchEvent
chunsoft: ViewGroupA onTouchEvent

(3)部长(MyViewGroupB)拦截事件传递
让部长(MyViewGroupB)使用onInterceptTouchEvent()方法返回true拦截事件传递,log日志如下:

chunsoft: ViewGroupA dispatchTouchEvent
chunsoft: ViewGroupA ViewGroupA onInterceptTouchEvent
chunsoft: ViewGroupB dispatchTouchEvent
chunsoft: ViewGroupB ViewGroupA onInterceptTouchEvent
chunsoft: ViewGroupB onTouchEvent
chunsoft: ViewGroupA onTouchEvent

(3)事件处理
一般情况下,当你(MyView)处理完任务会向上级报告,需要上级确认,所以你的事件处理返回false。突然有一天你受不了老板的压迫,罢工,那么你的任务没人做,也不用报告上级,直接返回true,Log如下所示:

chunsoft: ViewGroupA dispatchTouchEvent
chunsoft: ViewGroupA ViewGroupA onInterceptTouchEvent
chunsoft: ViewGroupB dispatchTouchEvent
chunsoft: ViewGroupB onInterceptTouchEvent
chunsoft: View dispatchTouchEvent
chunsoft: View onTouchEvent

事件传递跟以前一样,但是事件处理,到你(MyView)这就结束了,因为你返回True,表示不用向上级汇报了。

假如你(MyView)发送了报告,如果部长觉得你的报告太丢人,不给总经理看,返回True,即将MyViewGroupB的onTouchEvent返回Ture,Log如下所示,

chunsoft: ViewGroupA dispatchTouchEvent
chunsoft: ViewGroupA ViewGroupA onInterceptTouchEvent
chunsoft: ViewGroupB dispatchTouchEvent
chunsoft: ViewGroupB onInterceptTouchEvent
chunsoft: View dispatchTouchEvent
chunsoft: View onTouchEvent
chunsoft: ViewGroupB onTouchEvent

(5)底层View阻止父层的View截获touch事件
底层的View能够接收到这次的事件有一个前提条件:在父层级允许的情况下。假设不改变父层级的dispatch方法,在系统调用底层onTouchEvent之前会先调用父View的onInterceptTouchEvent方法判断,父层View是不是要截获本次touch事件之后的action。如果onInterceptTouchEvent返回了true,那么本次touch事件之后的所有action都不会再向深层的View传递,统统都会传给父层View的onTouchEvent,就是说父层已经截获了这次touch事件,之后的action也不必询问onInterceptTouchEvent,在这次的touch事件之后发出的action时onInterceptTouchEvent不会再次调用,直到下一次touch事件的来临。如果onInterceptTouchEvent返回false,那么本次action将发送给更深层的View,并且之后的每一次action都会询问父层的onInterceptTouchEvent需不需要截获本次touch事件。只有ViewGroup才有onInterceptTouchEvent方法,因为一个普通的View肯定是位于最深层的View,touch事件能够传到这里已经是最后一站了,肯定会调用View的onTouchEvent。
对于底层的View来说,有一种方法可以阻止父层的View截获touch事件,就是调用getParent().requestDisallowInterceptTouchEvent(true);方法。一旦底层View收到touch的action后调用这个方法那么父层View就不会再调用onInterceptTouchEvent了,也无法截获以后的action。

实例

(1)子控件和父控件的事件响应问题
当父控件中有子控件的时候,并且父控件和子空间都有事件处理(比如单击事件)。这时,点击子控件,父控件的单击事件就无效了。

比如一个LinearLayout里面有一个子控件TextView,但是TextView的大小没有LinearLayout大。

①如果LinearLayout和TextView都设置了单击事件,那么点击TextView区域的时候,触发的是TextView的事件,点击TextView以外的区域的时候,还是触发的LinearLayout的事件。

②如果LinearLayout设置了单击事件,而TextView没有设置单击事件的话,那么不管单击的是TextView区域,还是TextView以外的区域,都是触发的LinearLayout的单击事件。

如果LinearLayout的大小和TextView一样的话,那么

①如果LinearLayout和TextView都设置了单击事件,那么只有TextView的单击事件有效。

②如果LinearLayout设置了单击事件,而TextView没有设置单击事件的话,那么触发的是LinearLayout的单击事件。

(2)onTouch()和onTouchEvent()区别
源码

    public boolean dispatchTouchEvent(MotionEvent event){
        ... ...
        if(onFilterTouchEventForSecurity(event)){
            ListenerInfo li = mListenerInfo;
            if(li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
                && li.mOnTouchListener.onTouch(this, event)) {
                return true;
            }
            if(onTouchEvent(event)){
                return true;
            }
        }
        ... ...
        return false;
    }

从源码中可以看出,这两个方法都是在View的dispatchTouchEvent中调用的,onTouch优先于onTouchEvent执行。如果在onTouch方法中通过返回true将事件消费掉,onTouchEvent将不会再执行。

onTouch能够得到执行需要两个前提条件,第一mOnTouchListener的值不能为空,第二当前点击的控件必须是enable的。因此如果你有一个控件是非enable的,那么给它注册onTouch事件将永远得不到执行。对于这一类控件,如果我们想要监听它的touch事件,就必须通过在该控件中重写onTouchEvent方法来实现。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值