想必很多android开发者都遇到过手势冲突的情况,我们一般都是通过内部拦截和外部拦截法解决此类问题。要想搞明白原理就必须了解View的分发机制。在此之前我们先来了解一下以下三个非常重要的方法:

dispatchTouchEvent()

onInterceptTouchEvent()

onTouchEvent()

我们分别看看这三个方法:

dispatchTouchEvent()
该方法是用来处理事件的分发。如果事件能够传递到当前View,那么一定会调用此方法。View中该方法的源码:

**     * Pass the touch screen motion event down to the target view, or this     * view if it is the target.     *     * @param event The motion event to be dispatched.     * @return True if the event was handled by the view, false otherwise.     */    public boolean dispatchTouchEvent(MotionEvent event) {        // If the event should be handled by accessibility focus first.        if (event.isTargetAccessibilityFocus()) {            // We don't have focus or no virtual descendant has it, do not handle the event.            if (!isAccessibilityFocusedViewOrHost()) {                return false;            }            // We have focus and got the event, then use normal event dispatch.            event.setTargetAccessibilityFocus(false);        }        boolean result = false;        if (mInputEventConsistencyVerifier != null) {            mInputEventConsistencyVerifier.onTouchEvent(event, 0);        }        final int actionMasked = event.getActionMasked();        if (actionMasked == MotionEvent.ACTION_DOWN) {            // Defensive cleanup for new gesture            stopNestedScroll();        }        if (onFilterTouchEventForSecurity(event)) {            //noinspection SimplifiableIfStatement            ListenerInfo li = mListenerInfo;            if (li != null && li.mOnTouchListener != null                    && (mViewFlags & ENABLED_MASK) == ENABLED                    && li.mOnTouchListener.onTouch(this, event)) {                result = true;            }            if (!result && onTouchEvent(event)) {                result = true;            }        }        if (!result && mInputEventConsistencyVerifier != null) {            mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);        }        // Clean up after nested scrolls if this is the end of a gesture;        // also cancel it if we tried an ACTION_DOWN but we didn't want the rest        // of the gesture.        if (actionMasked == MotionEvent.ACTION_UP ||                actionMasked == MotionEvent.ACTION_CANCEL ||                (actionMasked == MotionEvent.ACTION_DOWN && !result)) {            stopNestedScroll();        }        return result;    }

代码比较长,可以简化来看:

public boolean dispatchTouchEvent(Motion e){     boolean result=false;     if(onInterceptTouchEvent(e)){     //如果当前View截获事件,那么事件就会由当前View处理,即调用onTouchEvent()        result=onTouchEvent(e);     }else{        //如果不截获那么交给其子View来分发        result=child.dispatchTouchEvent(e);     }     return result;}

从以上可以看出,这三个方法的联系。

onInterceptTouchEvent()
该方法在上边可以看出,是在dispatchTouchEvent中调用,来判断自己是否需要截取事件,如果该方法返回为true,那么View将消费该事件,即会调用onTouchEvent()方法。如果返回false,那么通过调用子View的dispatchTouchEvent()将事件交由子View来处理。

onTouchEvent()
和onInterceptTouchEvent()一样也是在dispatchTouchEvent中调用的。用来处理点击事件,包括ACTION_DOWN,ACTION_MOVE,ACTION_UP。如果返回结果为false表示不消费该事件,并且也不会截获接下来的事件序列。如果返回为true表示当前View消费该事件。
在这里要强调View 的OnTouchListener。如果View设置了该监听,那么OnTouch()将会回调。如果返回为true那么该View的OnTouchEvent将不会在执行这是因为设置的OnTouchListener执行时的优先级要比onTouchEvent高。
还有我们平时很熟悉的onClickListener,其优先级比上边两个都低。

这三个方法在处理View的冲突时经常遇到,所以要熟悉其机制,看完这三个方法,现在来看看事件分发机制。

为了能够清楚的分析,我们先新建一个项目,布局如图:

ViewGroup1嵌套ViewGroup2,然后在嵌套CustomView(这里我继承自TextView)。布局很简单,只是为了说明ViewGroup的分发过程。
  然后在相应的View中重写dispatchTouchEvent,OnInterceptTouchEvent ,OnTouchEvent方法(CustomView没有OnInterceptTouchEvent),并在每个方法中输出相应的Log:

@Override    public boolean dispatchTouchEvent(MotionEvent ev) {        Log.e("ViewGroup1","ViewGroup1 dispatchTouchEvent");        return super.dispatchTouchEvent(ev);    }    @Override    public boolean onInterceptTouchEvent(MotionEvent ev) {        Log.e("ViewGroup1","ViewGroup1 onInterceptTouchEvent");        return super.onInterceptTouchEvent(ev);    }    @Override    public boolean onTouchEvent(MotionEvent event) {        Log.e("ViewGroup1","ViewGroup1 onTouchEvent");        return super.onTouchEvent(event);    }

CustomView中:

@Override    public boolean dispatchTouchEvent(MotionEvent event) {        Log.e("CustomView", "CustomView dispatchTouchEvent");        return super.dispatchTouchEvent(event);    }    @Override    public boolean onTouchEvent(MotionEvent event) {        Log.e("CustomView", "CustomView onTouchEvent");        return super.onTouchEvent(event);    }

运行程序,点击CustomView会得到如下Log信息:

 E/ViewGroup1: ViewGroup1 dispatchTouchEvent E/ViewGroup1: ViewGroup1 onInterceptTouchEvent E/ViewGroup2: ViewGroup2 dispatchTouchEvent E/ViewGroup2: ViewGroup2 onInterceptTouchEvent E/CustomView: CustomView dispatchTouchEvent E/CustomView: CustomView onTouchEvent E/ViewGroup2: ViewGroup2 onTouchEvent E/ViewGroup1: ViewGroup1 onTouchEvent

从Log信息可以看出,首先会先执行ViewGroup1 dispatchTouchEvent方法,上文中我们讲过dispatchTouchEvent()方法的内部逻辑:

public boolean dispatchTouchEvent(Motion e){     boolean result=false;     if(onInterceptTouchEvent(e)){     //如果当前View截获事件,那么事件就会由当前View处理,即调用onTouchEvent()        result=onTouchEvent(e);     }else{        //如果不截获那么交给其子View来分发        result=child.dispatchTouchEvent(e);     }     return result;}

在ViewGroup1 dispatchTouchEvent方法调用后,接着会调用onInterceptTouchEvent()来判断是否需要截取事件,默认是不截取的。事件会传递到ViewGroup1的子View也就是ViewGroup2。即ViewGroup2 的dispatchTouchEvent方法被调用,直到CustomView。当事件传递到CustomView后,同样是CustomView的dispatchTouchEvent方法会执行。可以看出,整个事件的分发是从ViewGroup1向CustomView传递的。此时如果CustomView 不能处理改事件,也就是说CustomView的OnTouchView方法返回为false,那么事件会向上交给ViewGroup2的OnTouchEvent()事件处理,以此类推:

如果ViewGroup2的onInterceptTouchEvent()返回为true,即要拦截事件,又会出现什么情况呢?

 @Override    public boolean onInterceptTouchEvent(MotionEvent ev) {        Log.e("ViewGroup2","ViewGroup2 onInterceptTouchEvent");        return true;    }

运行结果:

E/ViewGroup1: ViewGroup1 dispatchTouchEventE/ViewGroup1: ViewGroup1 onInterceptTouchEventE/ViewGroup2: ViewGroup2 dispatchTouchEventE/ViewGroup2: ViewGroup2 onInterceptTouchEventE/ViewGroup2: ViewGroup2 onTouchEventE/ViewGroup1: ViewGroup1 onTouchEvent

CustomView的dispatchTouchEvent()没有执行,事件没有传递给CustomView,而是直接调用了ViewGroup2的ViewGroup2 的onTouchEvent。

其实这也是很好理解的,和我们日常生活中办事流程是一样的,公司老板(ViewGroup1)把任务交给经理(ViewGroup2),经理在把工作交给员工(CustomView)。这里如果经理(ViewGroup2)觉得这件事自己可以完成就截获这个工作(onInterceptTouchEvent返回为true),就不会再将任务分配给员工(CustomView)处理了。
  说完分发流程,现在来看看事件的响应机制,我们将事件传递到CustomView,并且将CustomView的OnTouchEvent事件返回为true,得到以下结果:

E/ViewGroup1: ViewGroup1 dispatchTouchEventE/ViewGroup1: ViewGroup1 onInterceptTouchEventE/ViewGroup2: ViewGroup2 dispatchTouchEventE/ViewGroup2: ViewGroup2 onInterceptTouchEventE/CustomView: CustomView dispatchTouchEventE/CustomView: CustomView onTouchEvent

我们会发现ViewGroup2 的OnTouchEvent不会在执行了,还是用我们刚刚的例子来说,就是员工(CustomView)在接到任务后顺利的完成了任务,就不必在劳烦上司响应处理了。只有在员工(CustomView)无法完成这项工作时(OnTouchEvent返回为false)时才会请求经理帮忙(ViewGroup2 调用OnTouchEvent方法),同样的,如果经理也不能搞定的话,就交给老板了。
  如果有兴趣的话,可以去研究研究源码。

更多相关文章

  1. stagefright概述
  2. ios 开发之基础控件
  3. android 事件模型
  4. [原]Android上GTalk以及Push机制的XMPP数据选择使用protobuf格式
  5. Android(安卓)dispatchTouchEvent源码分析
  6. Android上下左右手势滑动事件处理
  7. Android(安卓)Mms之短信接收流程--从Framework到App
  8. Android实现BaseAdapter布局的两种方法
  9. ERROR: Unknown command 'crunch' 解决方法

随机推荐

  1. Android 提示框
  2. 在Android中使用Gradle
  3. Mac os Android 源码开发环境搭建
  4. unity游戏在安卓按home或者锁屏键后不能
  5. android 实现拖动效果
  6. getActionBar() return null
  7. android 通过构造创建进度对话框
  8. android 设置应用程序装在T卡的方法
  9. Android中使用自定义view实现轮播图
  10. Android 跳转到应用市场,评价App