浅谈Android事件分发机制

Android事件分发分为View和ViewGroup的分发,由dispatchTouchEvent,onInterceptTouchEvent,onTouchEvent三个方法共同完成分发过程。

View的事件分发

View的事件分发由dispatchTouchEvent,onTouchEvent两个方法完成。
我们先来看一下一个完整事件的执行流程。
在页面上有一个TestTextView(继承自TextView)。为了方便查看结果,在TestTextView中的dispatchTouchEvent,onTouchEvent方法中打了Log日志

   @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                Log.e("tag","=====  onTouchEvent "+ event.getAction());
                break;
            case MotionEvent.ACTION_UP:
                Log.e("tag","=====  onTouchEvent " + event.getAction());
                break;
            case MotionEvent.ACTION_MOVE:
                Log.e("tag","=====  onTouchEvent " + event.getAction());
                break;
        }
        return super.onTouchEvent(event);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                Log.e("tag","=====  dispatchTouchEvent " + event.getAction());
                break;
            case MotionEvent.ACTION_UP:
                Log.e("tag","=====  dispatchTouchEvent " + event.getAction());
                break;
            case MotionEvent.ACTION_MOVE:
                Log.e("tag","=====  dispatchTouchEvent " + event.getAction());
                break;
        }
        return super.dispatchTouchEvent(event);
    }

设置TestTextView的点击事件和touch监听

      tv_touch.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Log.e("tag","========   onClick");
            }
        });

        tv_touch.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                switch (event.getAction()){
                    case MotionEvent.ACTION_DOWN:
                        Log.e("tag","=====  onTouch "+ event.getAction());
                        break;
                    case MotionEvent.ACTION_UP:
                        Log.e("tag","=====  onTouch " + event.getAction());
                        break;
                    case MotionEvent.ACTION_MOVE:
                        Log.e("tag","=====  onTouch " + event.getAction());
                        break;
                }
                return false;
            }
        });

点击TextView查看Log日志:

11-24 16:20:46.870 10998-10998/com.zy.touchevent E/tag: =====  dispatchTouchEvent 0
11-24 16:20:46.871 10998-10998/com.zy.touchevent E/tag: =====  onTouch 0
11-24 16:20:46.871 10998-10998/com.zy.touchevent E/tag: =====  onTouchEvent 0
11-24 16:20:46.897 10998-10998/com.zy.touchevent E/tag: =====  dispatchTouchEvent 1
11-24 16:20:46.897 10998-10998/com.zy.touchevent E/tag: =====  onTouch 1
11-24 16:20:46.897 10998-10998/com.zy.touchevent E/tag: =====  onTouchEvent 1
11-24 16:20:46.898 10998-10998/com.zy.touchevent E/tag: =====  onClick
从Log日志上可以看出,点击页面上的一个View,事件传递的方法顺序是 dispatchTouchEvent ->onTouch ->onTouchEvent->onClick ;

先看dispatchTouchEvent 方法

     /**
     * 分发事件,因为是view的事件分发此时分发的对象是自己
     * 返回值是boolean类型,表示是否消费当前事件
     * @param event
     * @return
     */
    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        return super.dispatchTouchEvent(event);
    }

看一下dispatchTouchEvent的源码:

boolean result = false;//返回结果,默认false不处理
...
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
        && (mViewFlags & ENABLED_MASK) == ENABLED
        && li.mOnTouchListener.onTouch(this, event)) {
   result = true;
 }      
//前面如果没有处理,result为false,此时result结果受onTouchEvent
if (!result && onTouchEvent(event)) {
      result = true; 
}    

从上面代码可以看出,result默认为false不处理,且result的结果在两个if中确定。
第一个if判断 li != null在if上面已经初始化,li!=null成立;
mOnTouchListener 在我们设置setOnTouchListener监听的时候已经赋值,也不为null;
(mViewFlags & ENABLED_MASK) == ENABLED判断当前点击的控件是否是enable的,按钮默认都是enable的,成立;
最后一个条件li.mOnTouchListener.onTouch(this, event)的返回值受OnTouchListener事件中的onTouch()方法影响,如果在onTouch()中返回ture则result=true成立,就不会执行第二个if语句。
第二个if判断当第一个if判断不成立时,此时result的结果受自身onTouchEvent影响;

既然dispatchTouchEvent的返回值受onTouch()方法影响,onTouch()的默认返回值为false,我们修改为true看看此时的打印日志:

11-24 17:32:13.263 18052-18052/com.zy.touchevent E/tag: =====  dispatchTouchEvent 0
11-24 17:32:13.264 18052-18052/com.zy.touchevent E/tag: =====  onTouch 0
11-24 17:32:13.316 18052-18052/com.zy.touchevent E/tag: =====  dispatchTouchEvent 1
11-24 17:32:13.316 18052-18052/com.zy.touchevent E/tag: =====  onTouch 1

从Log日志我们可以发现onTouchEvent,onclick方法没了,这很好理解。因为onTouch 方法返回true表示将此次事件消费掉,而onTouchEvent方法在onTouch 后面执行,事件自然也就执行不到onTouchEvent和onclick。

既然onTouch 方法能够影响onTouchEvent的执行,那么onTouchEvent又和onclick有没有一腿呢?
来看看onTouchEvent方法的源码(源码有点多,省略和onclick不相关部分)

public boolean onTouchEvent(MotionEvent event) {
       switch (action) {
                case MotionEvent.ACTION_UP:
                        if (!post(mPerformClick)) {
                                    performClick();
                                }
                      break;
        }
}

public boolean performClick() {
        final boolean result;
        final ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnClickListener != null) {
            playSoundEffect(SoundEffectConstants.CLICK);
            li.mOnClickListener.onClick(this);
            result = true;
        } else {
            result = false;
        }

        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);

        notifyEnterOrExitForAutoFillIfNeeded(true);

        return result;
    }

当手指抬起时,performClick();方法会被调用,点击查看方法,可以知道当li.mOnClickListener != null不为空时,li.mOnClickListener.onClick(this);view的点击方法就会被调用。

View的事件分发就说到这里了,下面来说说ViewGroup的事件分发:

ViewGroup的事件分发
浅谈Android事件分发机制_第1张图片
gv.jpg

我觉得这张图是最能简单说明ViewGroup的事件分发顺序了,所以我就借鉴一下 ☺
当一个事件(点击或者touch)到达activity的根节点也就是ViewGroup时,ViewGroup会把事件遍历分发给它包含的每个子View,而当子View为ViewGroup时,又会通过调用ViwGroup的dispatchTouchEvent方法继续调用其内部的View的dispatchTouchEvent方法。所以上图的分发顺序是① -- ② -- ⑤ -- ⑥ -- ⑦ -- ③ -- ④。如果在分发过程中任何一个子View的dispatchTouchEvent的返回值为true(消费事件),后面的分发都会中止。
看一下ViewGroup的源码吧,很长,会省略很多,我开始看不懂也是参考别人的讲解看的,文章末尾会给链接的。

1、dispatchTouchEvent方法
   @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        ...
        if (disallowIntercept || !onInterceptTouchEvent(ev)) {  ①
            ...
            for (int i = count - 1; i >= 0; i--) {
                final View child = children[i];
                if (child.dispatchTouchEvent(ev))  {    ②
                    mMotionTarget = child;
                    return true;
                }
            }
        }
        return target.dispatchTouchEvent(ev);
    }

这里只给了几处影响dispatchTouchEvent方法返回值的代码,第一个判断里面两个条件满足其中一个就会进入后面代码,disallowIntercept是指是否禁用掉事件拦截的功能,默认是false,也可以通过调用requestDisallowInterceptTouchEvent方法对这个值进行修改。第二个判断是对onInterceptTouchEvent(拦截事件)返回值取反。如果一个控件是可点击的,那么点击该控件时,dispatchTouchEvent的返回值必定是true,第二个判断条件child.dispatchTouchEvent(ev)为true成立。
ViewGroup也是View的子类,因此View的事件分发在ViewGroup中同样适用。当ViewGroup中没有子View时,走的就是View的分发过程了

    final View target = mMotionTarget;
    if (target == null) {  
        ev.setLocation(xf, yf);  
        if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {  
            ev.setAction(MotionEvent.ACTION_CANCEL);  
            mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;  
        }  
        return super.dispatchTouchEvent(ev);  
    }  
2、onInterceptTouchEvent方法
public boolean onInterceptTouchEvent(MotionEvent ev) {  
    return false;  
}

上面就是onInterceptTouchEvent方法的源码,默认返回false不拦截,我们可以在需要的时候进行拦截。

3、onTouchEvent方法

刚刚也已经说了,ViewGroup也是View的一个子类。因此当ViewGroup没有子View时onTouchEvent方法会被调用,还有就是子View的onTouchEvent方法返回值为false,父容器的onTouchEvent方法会被调用。
我在开发艺术探索里看见了一个很好理解的比喻:假如点击事件是一个难题,这个难题被上级领导分配给一个程序员处理(类似事件分发过程),结果这个程序员搞不定(onTouchEvent返回了false),但是难题又必须要解决,那就只能交给水平更高的上级解决了(上级的onTouchEvent方法被调用),如果上级在搞不定,那只能交给上级的上级去解决,就这样将难题一层层上抛。

最后关于事件分发的总结:
1、Touch事件分发中只有两个主角:ViewGroup和View。包含onInterceptTouchEvent、
dispatchTouchEvent、onTouchEvent三个相关事件,其中ViewGroup又继承于View。
2、一个点击事件产生后,他的传递过程遵循如下顺序: Activity -> Window -> View
3、触摸事件由Action_Down、Action_Move、Aciton_UP组成,其中一次完整的触摸事件中,Down和Up都只
有一个,Move有若干个,可以为0个。

总结:第一次写这种总结分析类的文章,还是很困难的,首先我没有一个贯穿全文的思路(想了几个,被写死了),然后就是不知道该怎么写(虽然我心里明白是啥回事,但说不出来啊/(ㄒoㄒ)/),所以这篇文章里面我借鉴了好多别人的东西,包括思路,代码片段(希望不会被瞧不起)。哈哈,虽然写的不好,但还是有收获的,继续加油~

参考:
《安卓开发艺术探索》
Android事件分发机制完全解析,带你从源码的角度彻底理解(下)
Android:30分钟弄明白Touch事件分发机制

你可能感兴趣的:(浅谈Android事件分发机制)