谈一谈Android中的事件分发及面试常考问题

Android中的事件分发对很多人来说并不陌生,它可以说是Android的重点难点,也是面试经常会问的基础知识,很多人都搞不太清楚事件分发的过程。今天我们就来谈一谈Android中事件分发的过程原理。

事件

什么叫做一个事件?手指按下到抬起的一个过程称之为事件。在Android中把事件定义为,手指按下(DOWN),到手指抬起(UP),中间由零个、一个或者无数个移动(MOVE)连接起来称之为一个事件。 主要发生Touch事件的有以下四种:

  • MotionEvent.ACTION_DOWN:按下View,即事件开始。

  • MotionEvent.ACTION_UP:抬起View,即事件结束。

  • MotionEvent.ACTION_MOVE:移动View。

  • MotionEvent.ACTION_CANCEL:事件取消,非人为因素造成事件取消。

事件分发作用的对象以及方法

事件分发作用的三个对象:

Activity: 一般来说为系统层面上,其为最高级别的view。

ViewGroup: 能够包裹子控件的都可以称之为ViewGroup,例如我们常使用的三大布局,LinearLayout、RelativeLayouy以及FrameLayout都可以称之为ViewGroup这个级别。

View: 子控件,内部只能有自己本身,不能在包含其他控件。例如常用的TextView、Button以及ImageView都属于View这个级别。

当一个点击事件作用在view上时候,事件作用对象的过程为:Activity -> ViewGroup -> View

事件分发的三个方法:

  • dispatchTouchEvent(): 用于分发传递事件,当点击事件能够传递给当前View时候,此方法被调用。

  • onInterceptTouchEvent(): 用于判断事件是否拦截不往下传递(注意,此方法只在ViewGroup中才有,在Activity与View中并不存在此方法)。并且此方法在dispatchTouchEvent()中才会调用。

  • onTouchEvent(): 用于处理点击事件,此方法仍然是在dispatchTouchEvent()中调用。

如果以上三个方法按照优先级别排列,那么dispatchTouchEvent > onInterceptTouchEvent >onTouch。 等于说只有在dispatchTouchEvent中事件同意进行分发,后续的事件才会传递到onInterceptTouchEvent与onTouchEvent中。这里用一段伪代码来理解三者之间的关系。

// 点击事件产生后,会直接调用dispatchTouchEvent()方法
public boolean dispatchTouchEvent(MotionEvent ev) {
 
    //代表是否消耗事件
    boolean consume = false;
 
 
    if (onInterceptTouchEvent(ev)) {
    //如果onInterceptTouchEvent()返回true则代表当前View拦截了点击事件
    //则该点击事件则会交给当前View进行处理
    //即调用onTouchEvent ()方法去处理点击事件
      consume = onTouchEvent (ev) ;
 
    } else {
      //如果onInterceptTouchEvent()返回false则代表当前View不拦截点击事件
      //则该点击事件则会继续传递给它的子元素
      //子元素的dispatchTouchEvent()就会被调用,重复上述过程
      //直到点击事件被最终处理为止
      consume = child.dispatchTouchEvent (ev) ;
    }
 
    return consume;
   }

复制代码

onInterceptTouchEvent与onTouchEvent都包含在dispatchTouchEvent中,当if判断中的onInterceptTouchEvent返回为true(即代表拦截)之后才会执行onTouchEvent事件,否则继续传递给子元素的dispatchTouchEvent事件。

事件分发流程分析:

事件分发作用的无非就是三个对象Activity、ViewGroup以及View,作用的方法无非为三个方法dispatchTouchEvent()、onInterceptTouchEvent()和onTouchEvent()。具体作用方法入下图所示:

而整个事件分发机制的流程如下图所示

其中:

  • super:调用父类方法

  • true:消费事件,事件不在继续往下分发

  • false:不消费事件,事件交给父控件的onTouchEvent去处理或向下传递

下面逐一分析流程。

dispatchTouchEvent()

作用对象为Activity、ViewGroup以及View。当事件点击时,首先被调用的就是这个方法,再根据这个方法的返回值来判断事件是直接消费掉还是向下分发。

  • true:代表消费掉这个事件,事件不会再往下分发而是直接结束掉。

  • super:向下传递给onInterceptTouchEvent,如果作用在Activity上,由于Activity没有onInterceptTouchEvent方法所以直接传递给子类的dispatchTouchEvent方法;如果作用在View上,则直接传递给自己的onTouchEvent方法去处理。

  • false:代表不消费掉这个事件,事件则被传递给父类的onTouchEvent去处理。由于Activity中没有父类,所以直接停止掉这个事件。

当down触发了这个事件之后,不管到底返回的是哪个,最后的MOVE或者UP都还会再经过一次这个方法,这点和onTouchEvent与onInterceptTouchEvent不一样。

onTouchEvent()

作用对象为Activity、ViewGroup以及View。只有当拦截器onInterceptTouchEvent返回true进行拦截或者dispatchTouchEvent返回false,才会触发这个方法。不过这里需要注意,onInterceptTouchEvent拦截之后是触发自身View的onTouchEvent方法,而dispatchTouchEvent返回false则是触发父类的onTouchEvent方法。

  • true:代表处理这个事件。则MOVE与UP操作才会被调用

  • super/false:代表不处理或者说没有能力处理这个事件。则传递给父类的onTouchEvent去处理。

这里需要明确一点,我们平时在使用click事件作为点击的时候,其实click事件是包含在onTouch事件里面的。在onTouch事件里面其实包含了onTouchEvent、performClick以及Click事件,而我们一般使用的click事件其实优先级别是最低了,只有当前面的事件返回false不处理才会轮到click去触发处理,如果前面有一个返回了true那么事件都会被消费掉,不会传递给click去处理。具体操作我们后面再讲。

onInterceptTouchEvent()

作用对象仅为ViewGroup。意为拦截操作。事件传递过来之后,根据返回值的不同来判断是否需要进行拦截操作。

true:代表拦截这个事件。事件不会再往下传递,而是交给了自身的onTouch去处理。此方法返回true时,一个事件只调用一次,等于说后面的MOVE与UP不会再执行onInterceptTouchEvent方法,而是直接去执行onTouch方法。

super/false:代表不拦截这个事件,事件传递给子类View的dispatchTouchEvent去处理。

在使用拦截方法onInterceptTouchEvent的时候,需要注意两种特殊情况。

  1. 一次事件中,该方法一旦被调用返回了true就不会在调用了。假设在Down事件被调用时返回了true,那么Move与Up事件便不会再去调用这个方法,而是直接去执行onTouchEvent方法。

  2. 假设ViewGroup没有拦截Down事件,而是拦截了Move事件,那么这个Down会直接传递给子类View,而后面的Move由于被拦截了,但是他并不会传递给自身的onTouchEvent方法,而是被作为一个Cancel方法传递给子类的View,而后面再来的Move则会被传递给自身的View去处理,而子类的View也不再会收到后续事件。

讲了这么多理论,下面来实际操作一下。

首先先写一个MyActivity作为Activity覆写其中的dispatchTouchEvent与onTouchEvent方法;

再写一个MyLinearLayout作为ViewGroup并覆写dispatchTouchEvent、onInterceptTouchEvent与onTouchEvent方法;

最后再写一个MyView作为View覆写dispatchTouchEvent与onTouchEvent方法。如下图所示:

当我们点击MyView时候,来看下Log日志的输入情况。

可以看到,当点击MyView的时候,首先调用的是MyActivity中的dispatchTouchEvent;然后向下分发调用MyLinearLayout中的dispatchTouchEvent与onInterceptTouchEvent方法;接下来到了MyView的dispatchTouchEvent与onTouchEvent方法中,在onTouchEvent中返回false或者super代表不处理这个事件,事件则向上传递给父类方法去处理,最后再传递回MyActivity的onTouchEvent去处理。

这里的事件解释起来貌似很复杂,其实理解起来并不复杂。这里做个形象的比喻,我们可以把Activity比喻为公司经理,ViewGroup为部门组长,而View为底层员工。当点击View的时候代表公司要开始开发一个新项目,这个时候公司经理先把需求交给部门组长(dispatchTouchEvent)——>部门组长接到任务之后查看是否需要自己来开发(onInterceptTouchEvent是否返回true,true代表交给自己处理,false代表自己不处理交给下属去开发)——>下属得到组长的新项目任务之后仔细审核了需求,如果觉得自己能够做则自己处理(onTouchEvent返回为true),如果发现自己的能力不够则交给比自己厉害的组长去开发(onTouchEvent返回false向上传递)——>组长接到了下属传递过来不能处理的任务之后看看自己能不能处理,能处理则自己处理(onTouchEvent返回true),如果发现这个任务太难了自己也不能处理那么就交给经理去处理(此时TouchEvent就返回false)——>最后经理拿到任务之后如果能做就自己做,不能做那么这个任务就作废因为公司能力有限做不了,到此整个分发结束。


在面试当中,面试官常常会考察ViewGroup中事件分发的几种情况处理,下面我们分别来谈论事件分发的几个情况。

在ViewGroup中当dispatchTouchEvent拦截了Down事件之后,后续的MOVE与UP事件如何执行?

我们先将dispatchTouchEvent中的代码改造如下:

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    Log.i("MyLinearLayout======","dispatchTouchEvent");
    switch (ev.getAction()){
        case MotionEvent.ACTION_DOWN:
            Log.i("MyLinearLayout======","MotionEvent.ACTION_DOWN");
            return true;
 
 
        case MotionEvent.ACTION_MOVE:
            Log.i("MyLinearLayout======","MotionEvent.ACTION_MOVE");
 
 
        case MotionEvent.ACTION_UP:
            Log.i("MyLinearLayout======","MotionEvent.ACTION_UP");
            
    }
    return super.dispatchTouchEvent(ev);
}

复制代码

在dispatchTouchEvent中,在监听到Down事件的时候我们返回true,代表事件不分发,直接结束掉,此时我们移动手指头看看MOVE与UP事件如何处理。

可以看到,当点击Down返回true之后,事件会传递给上层Activity中的dispatchTouchEvent,之后的MOVE与UP事件仍然会继续传递到本ViewGroup中的dispatchTouchEvent方法里面处理。等于说,dispatchTouchEvent的返回值不论是true还是fasle并不影响后续MOVE与UP的事件操作,后续事件仍然会经过dispatchTouchEvent方法。特别注意这点与onTouchEvent和onInterceptTouchEvent方法不一样。


在onTouchEvent中返回true或者false对于后续MOVE、UP方法的影响?

我们将View中方法改造如下:

@Override
public boolean onTouchEvent(MotionEvent event) {
    //Log.i("MyView======","onTouchEvent");
    switch (event.getAction()){
        case MotionEvent.ACTION_DOWN:
            Log.i("MyView======","ACTION_DOWN");
            return true;
        case MotionEvent.ACTION_MOVE:
            Log.i("MyView======","ACTION_MOVE");
            return false;
        case MotionEvent.ACTION_UP:
            Log.i("MyView======","ACTION_UP");
 
 
    }
    return super.onTouchEvent(event);
}

复制代码
myView.setOnTouchListener(new View.OnTouchListener() {
    @Override
    public boolean onTouch(View v, MotionEvent event) {
        Log.d("onTouchListener======","onTouchListener");
        return false;
    }
});
 
myView.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        Log.d("setOnClickListener======","setOnClickListener");
 
    }
});

复制代码

点击View看下如何执行。

我们看到执行顺序为onTouch ——> onTouchEvent ——> onTouchListener。而此时将onTouch中的返回值设置为true,再点击一下,输出结果

可以看到,此时只执行了onTouch事件,后面的时间并没有跟随执行。而我们吧onTouch设置为false,把onTouchEvent设置为true再来看看

看到执行了onTouch与onTouchEvent事件,onclick事件并没有执行。由此可见,在点击事件中,事件的优先级为 onTouch > onTouchEvent > onClick,当优先级高的事件返回true之后,优先级低的事件就不会再执行了,如果想要执行,必须优先级高的事件返回false才可以执行。


在onInterceptTouchEvent中返回true时,MOVE与UP事件如何执行?

onInterceptTouchEvent在使用的时候必须要知道两点内容:

  1. onInterceptTouchEvent一旦返回true,也就是被拦截之后后续的MOVE、UP方法就不会在执行这个方法了,而是直接去执行自身的onTouch方法。onInterceptTouchEvent一旦被拦截,一次事件只执行一次,后续MOVE与UP不再执行。

  2. 假设此时有一个ViewGroupA,还有一个子ViewB;而当ViewGroupA在进行拦截的时候,并没有去拦截Down事件,而是去拦截了ViewGroupA的Move事件,此时Down事件会直接去ViewB,而第一个MOVE事件不会传给自身的onTouchEvent事件,而是以一个Cancel传递给ViewB,而后面的MOVE才传递给自身的onTouch事件。

到此,事件分发所有内容讲解完毕~

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值