View事件分发机制——书本篇


迷茫,是青春最真实的状态;但奋斗,才是青春的主基调;努力是打败焦虑的绝好方法!


事件分发机制概述

点击事件的传递过程概述

  • 分析对象,点击事件MotionEvent
  • 事件分发机制,其实就是对MotionEvent事件的分发过程
  • MotionEvent事件的分发过程就是当一个MotionEvent产生以后,系统需要把这个事件传递给一个具体的View,而这个传递的过程,就是分发过程
  • 点击事件的分发过程是由三个很重要的方法共同完成的:dispatchTouchEventonInterceptTouchEventonTouchEvent

三个方法的简述

public boolean dispatchTouchEvent(MotionEvent ev)

用来进行事件的分发。如果事件能传递当前的View,那么此方法一定会被调用,返回结果受当前View的onTouchEvent下级的dispatchTouchEvent方法影响,表示是否消耗当前事件

public boolean onInterceptTouchEvent(MotionEvent event)

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

public boolean onTouchEvent(MotionEvent event)

在dispatchTouchEvent方法中调用,用来处理点击事件,返回结果表示是否消耗当前事件,如果不消耗,则在同一个事件序列中,当前View无法再次接收到事件


三个方法的关系

//点击事件到达当前控件
public boolean dispatchTouchEvent(MotionEvent ev){
    //判断当前事件是否消耗View
    boolean consume = false;
    //如果拦截事件
    if(onInterceptTouchEvent(ev)){
       //判断当前View是否消耗事件
       consume = onTouchEvent(ev);
    }else{
       //不拦截,事件传递给子控件
       consume = child.dispatchTouchEvent(ev);
    }
    return consume; 
}

点击事件的传递规则
点击事件分发,由Activity到Window再到ViewGroup,然后才由事件分发机制接手。
对于一个根ViewGroup,点击事件到来时。

  1. 首先传递给它,这时它的dispatchTouchEvent就会被调用。
  2. 如果这个ViewGroup的onInterceptTouchEvent方法返回true,表示它拦截当前MotionEvent。
  3. 拦截了,事件就交给这个ViewGroup处理,即它的onTouchEvent方法被调用。
  4. 如果这个ViewGroup的onInterceptTouchEvent方法返回false,表示它不拦截当前MotionEvent。
  5. 不拦截,MotionEvent继续向下分发给它的子元素。
  6. 由于到达了子元素,子元素的dispatchTouchEvent方法就被调用。
  7. 循环反复,直到这个MotionEvent被处理。

当前View处理事件

  • 如果设置了OnTouchListener,那么OnTouchListener的onTouch方法会被回调。
  • 这时事件如何处理,看onTouch的返回值。若为false,则调用当前View的onTouchEvent方法;若为true,则不调用onTouchEvent方法。所以OnTouchListener的优先级高于onTouchEvent
  • 在onTouchEvent中,如果设置了OnClickListener,那么它的onClick方法被调用。所以onTouchEvent的优先级高于OnClickListener,OnClickListener处于事件传递的尾端

onTouchEvent处理事件

  • 如果拦截事件的这个View的onTouchEvent返回false,表示它不能处理这个事件,即不消耗当前事件;那么它的父容器的onTouchEvent会调用,去处理这个事件;依次循环,如果最后所有的元素都不处理这个事件,那么Activity的onTouchEvent方法会被调用,由Activity来处理这个事件。
  • 举个好理解的例子:有一个员工,接受了一个任务,但是他接受了,却没有能力去完成这个任务,那么就要把这个任务分发给更厉害的人,即他的上级人员,若是还不能完成,那么继续往上,找更厉害的人。若是所有的人都不能完成,那么最后就只能由最终的Boss来完成了。

事件分发机制的结论

  1. 同一个事件序列是一次滑动过程,即手指落到屏幕的时刻开始,到手指离开屏幕的时刻结束。即down事件开始,move为过程,up为结束。
  2. 正常情况下,一个事件序列只能被一个View拦截且消耗
  3. 如果一个元素拦截了某个事件,那么同一个事件序列内的所有事件都会直接交给它处理。因此,同一个事件序列中的事件不能分别由两个View同时处理,但是可以通过当前View的onTouchEvent方法强制将事件交给别的View处理,即onTouchEvent方法返回false,即当前View不处理这个事件。
  4. 某个View一旦决定拦截,那么同一个事件序列中的所有事件都只能由它处理(前提是事件序列可以到达这个View)。这个View的onInterceptTouchEvent方法不会被调用,因为已经决定拦截了,所以无需询问是否拦截了。
  5. 如果一个View已经开始处理事件,但是onTouchEvent返回false,即不消耗该事件,那么同一事件序列中的其他事件不会再交给它处理。这个事件交给它的父容器处理,调用父容器的onTouchEvent方法。即一个事件交给一个View处理,它就必须消耗掉,否则同一事件序列内的其他事件就不再交给它处理了。
  6. 如果View在一个事件序列中只消耗ACTION_DOWN事件,那么这个点击事件会消失。它的父容器的onTouchEvent不会调用,因为消耗了ACTION_DOWN事件(onTouchEvent返回true)。所以当前View可以持续接收到后续的事件,最终这些消失的事件由Activity处理。
  7. ViewGroup默认不拦截任何事件。ViewGroup的onInterceptTouchEvent方法默认返回false。因为事件要传递给View。
  8. View没有onInterceptTouchEvent方法。一旦有点击事件传递给它,它的onTouchEvent方法就会被调用。
  9. View的onTouchEvent默认都会消耗事件(返回true),除非它是不可点击的(clickable和longClickable同时为false)。View的longClickable属性默认为false;clickable属性分情况:Button的clickable属性默认true。TextView的clickable属性默认false。
  10. View的enable属性不影响onTouchEvent的默认返回值。哪怕View不可用,只要一个属性可点击,那么它的onTouchEvent就返回true。
  11. onClick会发生的前提是当前View是可点击的,并且它收到了down和up的事件。即被单击。
  12. 事件传递过程是由外向内的,即事件总是先传给父元素,然后再由父元素分发给子View,通过requestDisallowInterceptTouchEvent方法可以干预父元素的事件分发过程,但是ACTION_DOWN事件除外。

结合源码分析

Activity对MotionEvent的分发

  • Activity -> Window implements PhoneWindow -> DecorView。
  • 由Activity的dispatchTouchEvent来进行事件派发,具体工作是由Activity内部的Window来完成的。
  • Window会将事件传递给decor view,decor view一般就是当前界面的底层容器,即setContentView所设置的View的父容器。
  • decor view可以通过Activity.getWindow.getDecorView()获得。
                      源码:Activity#dispatchTouchEvent
public boolean dispatchTouchEvent(MotionEvent ev){
    if(ev.getAction() == MotionEvent.ACTION_DOWN){
       onUserInteraction();
    }
    if(getWindow().superDispatchTouchEvent(ev)){
       return true;
    }
    return onTouchEvent(ev);
}
  • 首先事件开始交给Activity所附属的Window进行分发。getWindow().superDispatchTouchEvent(ev);
  • 返回true,则整个事件循环结束。
  • 返回false,意味着事件没人处理,所有View的onTouchEvent都返回了false,那么Activity的onTouchEvent就会被调用。由Activity处理该事件。
    return onTouchEvent(ev);

Window对MotionEvent的分发

  • Window是个抽象类,Window的superDispatchTouchEvent方法也是个抽象方法,必须找到Window的实现类,即PhoneWindow
  • PhoneWindow是Window的实现类,这一点在Window的说明中可以看出,这里知道就好了,就不粘贴了。
  • 那段说明的主要意思就是:Window类可以控制顶级View的外观和行为策略,它的唯一实现位于android.policy.PhoneWindow中,当你要实例化这个Window类的时候,你并不知道它的细节,因为这个类会被重构,只有一个工厂方法可以使用。
  • 由于Window的唯一实现是PhoneWindow,所以点击事件传递到了PhoneWindow。
                   源码:PhoneWindow#superDispatchTouchEvent
public boolean superDispatchTouchEvent(MotionEvent event){
     return mDecor.superDispatchTouchEvent(event);
}

这里逻辑简单,PhoneWindow直接将事件给了DecorView。

                                源码:DecorView
private final class DecorView extends FrameLayout implements RootViewSurfaceTaker

//This is the top-level view of the window,containing the window decor.
   private DecorView mDecor;
   
   @Override
   public final View getDecorView(){
       if(mDecor == null){
          installDecor();
       }
       return mDecor;
   }
  • 通过((ViewGroup)getWindow().getDecorView().findViewById(android.R.id.content)).getChildAt(0)可以获取到Activity所设置的View。
  • mDecor是getWindow().getDecorView()返回的View。
  • 通过setContentView设置的View是它的一个子View。
  • 事件目前到了DecorView,而DecorView继承于FrameLayout且是父View,所以事件最终会传递给View。

所以换句话说,事件一定会传递到View。当前事件已经传递到顶级View了,即在Activity中通过setContentView所设置的View;另外顶级View又叫根View,一般来说都是ViewGroup


顶级View对MotionEvent的分发

  • 事件到达顶级View后(顶级View一般都是ViewGroup),调用顶级View的dispatchTouchEvent方法。
  • 如果顶级ViewGroup拦截事件,即onInterceptTouchEvent返回true,事件交给ViewGroup处理。
  • 如果ViewGroup的onTouchListener设置,则onTouch会被调用,onTouchEvent就不会被调用,所以二者是互斥关系。即都存在的话,onTouch会屏蔽onTouchEvent。
  • 在onTouchEvent中,如果设置了onClickListener,则onClick会被调用。
  • 如果顶级ViewGroup不拦截事件,则事件会传递给它的子View,即其子View的dispatchTouchEvent方法被调用。这时事件已经传到了下一层View,这时循环执行ViewGroup做过的操作,完成事件分发。

View对MotionEvent的处理

  • 这里的View代表的是只有一个元素,就是最终的View,没有子元素了。所以无法向下传递,只能自己处理
  • 首先判断有没有设置onTouchListener,如果有,并且其onTouch返回true,则onTouchEvent会被屏蔽。方便在外界处理点击事件。
  • 不可用状态的View照样会消耗事件,尽管它看起来是不可用的。只要其可点击即可。
  • 如果View设置了代理,还会执行TouchDelegate的onTouchEvent方法。
  • onTouchEvent对点击事件的具体处理:只要View是可点击的,就会消耗事件,即onTouchEvent返回true。
  • 当ACTION_UP事件发生时,会触发performClick方法,如果View设置了OnClickListener,那么performClick的内部会调用它的onClick方法。
  • setClickablesetLongClickable可以改变CLICKABLELONG_CLICKABLE属性。
  • setOnClickListener会自动将View的CLICKABLE设为true;setOnLongClickListener会自动将View的LONG_CLICKABLE设为true
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值