Android View的事件分发详解

 更新时间:2017年12月12日 08:52:24   作者:喵主子的阳光  
我们在学习View的时候,不可避免会遇到事件的分发,而往往遇到的很多滑动冲突的问题都是由于处理事件分发时不恰当所造成的。因此,深入了解View事件分发机制的原理,对于我们来说是很有必要的。

1.前言

    近两天学习了一下view的事件分发,把自己的理解总结了一遍,只表达了自己认为需要明白的地方,毕竟是菜鸟一枚,不对的地方还请大神们多指教!

2.三个方法

public boolean dispatchTouchEvent(MotionEvent ev)

用于事件的分发,返回结果受以下两个方法的影响,表示是否消耗了事件。

public boolean onInterceptTouchEvent(MotionEvent ev)

事件是否被拦截,返回true表示拦截,false表示不拦截

public boolean onTouchEvent(MotionEvent ev)

表示事件是否被处理

3.三个方法之间的关系

public boolean dispatchTouchEvent(MotionEvent ev){
  boolean result = false;
  if(onInterceptTouchEvent(ev)){
    result = onTouchEvent(ev);
  }else{
    result = child.dispatchTouchEvent(ev);
  }
  return result;
}

4.原理论述

1.同一个事件序列是指从手指接触屏幕开始到离开屏幕那一刻产生的一系列事件,其中以down开始,中间产生若干move事件,最后以up事件结束。
2.同一个事件序列,只能被一个view拦截处理,如果它不消耗down事件,那么其他的事件也不会交给它处理,而且一旦它处理了down事件,以后的事件便不会调用onInterceptTouchEvent此方法判断是否拦截,因为都会交给它处理,就不用再询问了。
3.如果view不消耗除down以外的其他事件,那么这个点击事件会消失,此时父元素的onTouchEvent并不会被调用,并且当前view可以持续接收后续事件,最终这些消失的点击事件将交由activity处理。
4.ViewGrop默认不拦截任何事件。
5.VIew没有onInterceptTouchEvent方法,一旦有点击事件传递给它,那么它的onTouchEvent方法就会调用。
6.View处于不可点击状态时(clickable和longClickable同时为false),onTouchEvent不会消耗事件。除此之外onTouchEvent默认都是消耗事件返回为true的。
7.View的enable属性不影响onTouchEvent的默认返回值
8.事件的传递过程是由外向内的,即事件都是先传递给父元素然后再分发给子元素。通过requestDisallowInterceptTouchEvent方法干预父元素的事件分发过程,但是ACTION_DOWN事件除外。
9.view中的点击事件优先级,高-低:onTouchListener--->onTouchEvent--->onClickListener,当onTouchListener中的onTouch方法返回false时,onTouchEvent才会调用

5.个别源码分析

这是ViewGroup的dispatchTouchEvent中拦截事件的一段源码,主要讲述拦截过程的思路
1.mFirstTouchTarget 表示当事件被ViewGroup的子元素成功处理时,会被赋值,所以一旦ViewGroup拦截事件,则mFirstTouchTarget ==null,则返回true。
2FLAG_DISALLOW_INTERCEPT此标志位是有requestDisallowInterceptTouchEvent方法设置的,一般位于子view中,一旦此标志位设置则不会执行VIewGroup中的onInterceptTouchEvent方法,也就不会拦截除了ACTION_DOWN之外的方法,印证了论述2

// Check for interception.
  final boolean intercepted;
  if (actionMasked == MotionEvent.ACTION_DOWN
      || mFirstTouchTarget != null) { 
    final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
    if (!disallowIntercept) {
      intercepted = onInterceptTouchEvent(ev);  
      ev.setAction(action); // restore action in case it was changed
    } else {
      intercepted = false;
    }
  } else {
    // There are no touch targets and this action is not an initial down
    // so this view group continues to intercept touches.
    intercepted = true;
  }
  ...
  // Check for cancelation.
  final boolean canceled = resetCancelNextUpFlag(this) || actionMasked == MotionEvent.ACTION_CANCEL;

相关文章

最新评论