Android事件分发机制

  1. Android 事件分发涉及到三个方法

     public boolean dispatchTouchEvent(MotionEvent ev) 
    

    这个方法用来分发事件的

    	 public boolean onInterceptTouchEvent(MotionEvent ev)
    

    这个方法是用来判断是否需要拦截事件的,如果返回true,表示拦截了事件,如果返回了false,说明没有拦截事件。如果View拦截了某个事件,那么在同一个事件序列中,此方法将不会被再次调用。这个方法只有ViewGroup才会有

     public boolean onTouchEvent(MotionEvent ev)
    

    这个方法表示是否消耗掉了触摸或者点击事件,返回true说明消耗掉了,返回false表明没有

  2. onTouchEvent()、OnTouchListener中的onTouch()方法和OnClickListener中的onClick()方法被调用的优先级问题

    当一个View需要处理事件时,如果它设置了OnTouchListener,那么OnTouchListener中的onTouch()方法就会被回调,如果onTouch()返回false,则当前View的onTouchEvent()方法会被回调,如果onTouch返回true,那么onTouchEvent()方法将不会被调用。
    由此可见,onTouch()方法得优先级比onTouchEvent()的要高。
    在OnTouchEvent()方法中,如果当前设置有OnClickListener,那么它的onClick方法会被调用,由此可见,平时我们常用的onClickListener其优先级最低,处于事件传递的尾端。

  3. 事件传递顺序
    当一个事件产生后,它的传递过程遵循如下顺序:Activity->Window->View,即事件总先是传递给Activity,再传递给Window,再传递给顶级View,顶级View接收到事件后,在按照事件传递的机制去分发事件

  4. View事件传递机制
    事件先传递给ViewGroup,此时ViewGroup的dispatchTouchEvent()会被触发执行,在dispatchTouchEvent方法里,接着会调用onInterceptTouchEvent()方法,如果onInterceptTouchEvent()方法返回true,说明此ViewGroup拦截了事件,事件不再往下传递,如果返回false,则说明此ViewGroup没有拦截事件,事件会传递到它的子View中去。

    如果此ViewGroup拦截了事件,则会调用此ViewGroup的onTouchEvent()方法,如果返回true,则说明ViewGroup消费了这个事件,如果返回false,则说明事件没有消费,事件会回传给上一级的View,如果上级View和Window都不做处理,则会回传给Activity。

    如果ViewGroup没有拦截事件,事件将会传递到它的子View中去,如果子View是个ViewGroup,那么事件将会像上一级ViewGroup传递的规则那样传递事件,如果子View是个普通的View,那么它的dispatchTouchEvent()方法将会被调用,因为普通的View没有onInterceptTouchEvent()方法,所以紧接着它的onTouchEvent方法会被调用(在没有注册OnTouchListener的情况下),如果onTouchEvent()方法返回true,说明事件被消费了,如果返回false,事件没有消费,会回传给它的上一级View。
    所以dispatchTouchEvent()方法返回true,事件会被当前View消费掉或者传给子View,如果返回fase,则事件会回传给上一级View。

  5. ACTION_DOWN(按下)、ACTION_MOVE(移动)、ACTION_UP(抬起)三种事件拦截规则以及调用父View的requestDisallowInterceptTouchEvent()方法对事件拦截规则的影响。

    View的拦截方法是onInterceptTouchEvent()方法,如果ACTION_DOWN被拦截,则这次操作的其他事件(ACTION_MOVE、ACTION_UP)都不会再触发onInterceptTouchEvent()方法了,它们默认和ACTION_DOWN一样,交由这个View的onTouchEvent()或者回传到上一级View进行处理。
    如果ACTION_DOWN没有被拦截,则这次操作的其他事件会继续传递到onInterceptTouchEvent()方法中,一般我们在处理滑动冲突的时候,会根据具体逻辑拦截ACTION_MOVE事件。
    默认情况下不会对ACTION_UP做拦截,因为拦截ACTION_UP后,子View就无法监听到onClick()方法了,因为点击包括了ACTION_DOWN和ACTION_UP两个事件

    如果在子View中调用了父View的requestDisallowInterceptTouchEvent()方法,那么它不会改变父View ACTION_DOWN事件的拦截规则,但是会改变ACTION_MOVE和ACTION_UP的拦截规则。比如:父View拦截了ACTION_DOWN事件,无论在子View中调用父View的requestDisallowInterceptTouchEvent(boolean b)传false或者是true,其他事件都会被拦截,不会再传到子View。如果父View没有拦截ACTION_DOWN事件,如果在子View中,调用父View的requestDisallowInterceptTouchEvent(true),则所有事件都会传递给子View。如果在子View中,调用了父View的requestDisallowInterceptTouchEvent(false)方法,则ACTION_MOVE和ACTION_UP是否拦截就取决于父View了。

    注:上面以及下文说的父View是指某个ViewGroup,子View是指这个ViewGroup里面嵌套的View,可能是普通的View,也可能是个ViewGroup

  6. 父View是怎么拦截事件的
    重写父View的onInterceptTouchEvent方法,分别对接收到的各个事件处理,return true说明是做了拦截,return false说明是没有做拦截。

      public boolean onInterceptTouchEvent(MotionEvent event) {
      	switch(event.getAction()) {
      		case MotionEvent.ACTION_DOWN:
      			return false;  //表示没有拦截ACTION_DOWN事件
      			//return true;  表示拦截了ACTION_DOWN事件
      			break;
      		case MotionEvent.ACTION_MOVE:
      			return false;  //表示没有拦截ACTION_MOVE事件
      			//return true;  表示拦截了ACTION_MOVE事件
      			break;
      		case MotionEvent.ACTION_UP:
      			return false;  //一般不对ACTION_UP做拦截			
     		}
      }
    
  7. View的滑动冲突处理方法
    View滑动冲突的产生一般是因为两层都能滑动的View嵌套在了一起导致的,当产生滑动事件的时候,究竟是父View处理还是子View处理,就会产生冲突。
    解决滑动冲突的方法一般有两种:外部拦截法和内部拦截法
    现在以父View水平滑动,子View竖直滑动为例,分别阐述一下外部拦截法和内部拦截法

    外部拦截法:所谓外部拦截法就是指触摸事件都交给父View处理,如果父View需要拦截此事件就拦截,不需要就不拦截。示例代码如下:

     private int mLastX;
     private int mLastY;
     @Override
     public boolean onInterceptTouchEvent(MotionEvent ev) {
         boolean intercepted = false;
         int x = (int) ev.getX();
         int y = (int) ev.getY();
         switch (ev.getAction()) {
             case MotionEvent.ACTION_DOWN:
                 intercepted = false;
                 break;
             case MotionEvent.ACTION_MOVE:
                 int deltaX = x - mLastX;
                 int deltaY = y - mLastY;
                 if (Math.abs(deltaX) > Math.abs(deltaY)) {
                     intercepted = true;
                 }else {
                     intercepted = false;
                 }
                 break;
             case MotionEvent.ACTION_UP:
                 intercepted = false;
                 break;
         }
         mLastX = x;
         mLastY = y;
         return intercepted;
     }
    

内部拦截法:内部拦截法是指父容器不拦截任何事件,所有的事件都传递给子元素,如果子元素需要处理触摸事件就消费掉,不需要处理就交由父容器进行处理,这种方法需要用到requestDisallowInterceptTouchEvent配合,示例代码如下:

//重写子元素的dispatchTouchEvent()方法
private int mLastX;
private int mLastY;
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    int x = (int) ev.getX();
    int y = (int) ev.getY();
    switch (ev.getAction()) {
        case MotionEvent.ACTION_DOWN:
            getParent().requestDisallowInterceptTouchEvent(true);
            break;
        case MotionEvent.ACTION_MOVE:
            int deltaX = x - mLastX;
            int deltaY = y - mLastY;
            if (Math.abs(deltaX) > Math.abs(deltaY)) {
                getParent().requestDisallowInterceptTouchEvent(false); 
            }
            break;
        case MotionEvent.ACTION_UP:
            break;
        default:
            break;
    }
    mLastX = x;
    mLastY = y;
    return super.dispatchTouchEvent(ev);
}

//重写父元素的onInterceptTouchEvent
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
        return false;
    }else {
        return true;
    }
}

Android事件分发机制就说到这里,如有错误请指正,谢谢!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值