Android View系列(二):事件分发机制源码解析

789 阅读13分钟

概述

在介绍点击事件规则之前,我们需要知道我们分析的是MotionEvent,即点击事件,所谓的事件分发就是对MotionEvent事件的分发过程,即当一个MotionEvent生成以后,系统需要把这个事件传递给具体的View,而这个传递过程就是分发过程,MotionEvent我们上节已经介绍过

事件分发主要涉及以下几个方法:

  • dispatchTouchEvent:用来进行事件的分发,如果事件可以传递到当前View那么此方法一定会被调用,返回结果受当前View的onTouchEvent和子View的dispatchTouchEvent方法影响,表示是否消耗当前事件
  • onInterceptTouchEvent:在上个方法内部调用,用来判断是否拦截事件,如果当前View拦截了事件,那么在同一时间序列内,此方法不会再次被调用,返回结果表示是否拦截事件
  • onTouchEvent:在dispatchTouchEvent方法中调用,用于事件的处理,返回值表示是否消耗事件,如果不消耗当前View无法再次接受到事件

这三个方法到底有什么关系?

我们先简述一下他们之间的关系,之后再进行源码的详细分析

当一个事件传递给一个根ViewGroup之后,这时他的dispatchTouchEvent就会被调用,进行事件的分发,如果该ViewGroup的onInterceptTouchEvent返回true,表示他要拦截此事件,接着这个事件就会交给ViewGroup处理,即他的onTouchEvent就会被调用,如果他的onInterceptTouchEvent返回fasle就表示不拦截此事件,这时就会把此事件传递给他的子View,接着子View的dispatchTouchEvent就会被调用,如此反复直到事件最终被处理

源码分析

当一个事件产生后,他的传递遵循如下顺序Activity→Window→View,即事件总是县传递给Activity,然后Activity传递给Window,最后Window传递给顶级View,顶级View接收到事件后,就会按照事件分发机制分发事件

Activity对事件的分发

当一个点击操作发生时,事件最先传递给当前的Activity,由Activity的dispatchTouchEvent进行分发,我们看下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处理,如果Window的分发返回true,表示事件就此结束,返回false,表示没有人处理,那么Activity的onTouchEvent就会被调用

Window对事件的分发

那么Window是怎么分发事件的呢?我们看下Window的源码,我们发现Window其实是一个抽象类,superDispatchTouchEvent也是一个抽象方法

public abstract boolean superDispatchTouchEvent(MotionEvent event);

那么Window的实现类是什么?其实是PhoneWindow,那我们看一下PhoneWindow是怎么处理事件的

 @Override
    public boolean superDispatchTouchEvent(MotionEvent event) {
        return mDecor.superDispatchTouchEvent(event);
    }

PhoneWindow直接把事件交给了DecorView,DecorView其实就是最顶层的View我们setContentView的View就是DecorView的一个子View,DecorView继承自FrameLayout,这个时候事件已经分发到了ViewGroup上

ViewGroup事件的分发

现在我们看一下ViewGroup的dispatchTouchEvent方法的源码

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
     ...
//--------TAG=1-------------------这里是一开始---------------------------------------------------
            //如果是Action_down 就对其先前所有的状态进行重置
            if (actionMasked == MotionEvent.ACTION_DOWN) {
                cancelAndClearTouchTargets(ev);
                resetTouchState();
            }
//--------TAG=2-----------------这里开始进行拦截验证-----------------------------------------------
            //如果是ACTION_DOWN,或者mFirstTouchTarget != null,就进行拦截验证
            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;
            }
//------------------------------------------------------------------------------------------------------------------------------

      ....

//----------TAG=3----------------这里看是遍历子view---------------------------------------------------------------

            //如果不拦截,并且不是cancel事件,就进行遍历子view分发事件
            if (!canceled && !intercepted) {

            ...

                //当ACTION_DOWN和ACTION_POINTER_DOWN和ACTION_HOVER_MOVE时候才会遍历子view
                if (actionMasked == MotionEvent.ACTION_DOWN
                        || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                        || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
                  
                        //找到可以接受触摸事件孩子,从前向后遍历查找
                        final View[] children = mChildren;
                        for (int i = childrenCount - 1; i >= 0; i--) {
                            final int childIndex = getAndVerifyPreorderedIndex(
                                    childrenCount, i, customOrder);
                            final View child = getAndVerifyPreorderedView(
                                    preorderedList, children, childIndex);

                     	 ...
                      
                            //判断触摸点是否在此View的范围中,是否在移动
                            if (!canViewReceivePointerEvents(child)
                                    || !isTransformedTouchPointInView(x, y, child, null)) {
                                ev.setTargetAccessibilityFocus(false);
                                continue;
                            }

           					...
           					
                            //分发事件,如果事件被子view消费,就跳出循环,不再继续分发给其他view
                            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                            
                               ...
                               
                                //addTouchTarget内部赋值mFirstTouchTarget=当前view
                                newTouchTarget = addTouchTarget(child, idBitsToAssign);
                                alreadyDispatchedToNewTouchTarget = true;
                                break;
                            }
            }
//-----------TAG=4-----------------这里已经遍历完了子view--------------------------------------------

            // //遍历完所有的子View后,还没有处理事件,就自己处理
            if (mFirstTouchTarget == null) {
                // No touch targets so treat this as an ordinary view.
                handled = dispatchTransformedTouchEvent(ev, canceled, null,
                        TouchTarget.ALL_POINTER_IDS);
            } else {
                //Action_Down之外的事件直接分发给目标view
                TouchTarget predecessor = null;
                TouchTarget target = mFirstTouchTarget;
                while (target != null) {
                    final TouchTarget next = target.next;
                    //如果上方遍历已经传递过改事件,则跳过本次事件
                    if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                        handled = true;
                    } else {
                        final boolean cancelChild = resetCancelNextUpFlag(target.child)
                                || intercepted;
                        if (dispatchTransformedTouchEvent(ev, cancelChild,
                                target.child, target.pointerIdBits)) {
                            handled = true;
                        }
                        ...
                }
            }
//------------------------------------------------------------------------------------------------------------------------------
            // Update list of touch targets for pointer up or cancel, if needed.
            if (canceled
                    || actionMasked == MotionEvent.ACTION_UP
                    || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
                resetTouchState();
            } else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
                final int actionIndex = ev.getActionIndex();
                final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
                removePointersFromTouchTargets(idBitsToRemove);
            }
        }

        if (!handled && mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
        }
        return handled;
    }

首先我们分析一下拦截事件的源码

   			//如果是ACTION_DOWN,或者mFirstTouchTarget != null,就进行拦截验证
            final boolean intercepted;
            if (actionMasked == MotionEvent.ACTION_DOW || 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;
            }

这段代码我们可以看到,有俩种情况会判断是否要拦截当前事件,事件类型是Action_Down,或者mFirstTouchTarget != null,ACTION_DOWN我们可以理解,mFirstTouchTarget != null代表什么呢?

我们从后面的代码可以看出,事件由ViewGroup的子元素处理成功时,mFirstTouchTarget被赋值并指向该子元素,也就是说当ViewGroup不拦截事件交由子元素处理时mFirstTouchTarget != null

一旦ViewGroup拦截事件mFirstTouchTarget != null就不成立,而当ACTION_MOVE ,ACTION_UP到来时,由于(actionMasked == MotionEvent.ACTION_DOWN|| mFirstTouchTarget != null)这个判断为false,ViewGroup的onInterceptTouchEvent不在会被调用,并且同一序列的其他事件,会默认交给ViewGroup处理

这里还有一种特殊情况,FLAG_DISALLOW_INTERCEPT标志位,这个标志位是通过requestDisallowInterceptTouchEvent来设置的,一般用于子View中,一旦FLAG_DISALLOW_INTERCEPT标志为被设置后,ViewGroup将无法拦截,除了ACTION_DOWN之外的其他事件,为什么要除了ACTION_DOWN呢,因为每当ACTION_DOWN带来都会重置FLAG_DISALLOW_INTERCEPT这个标记位,ACTION_DOWN事件总会调用自己的onInterceptTouchEvent询问是否拦截

强调一点requestDisallowInterceptTouchEvent,这个方法并不是万能的,执行他的前提是子View必须获取事件,假如父View的Down事件的onInterceptTouchEvent就返回true,拦截事件,那么子View做任何操作也不可能获取到事件

从上面分析我们可以得出结论

  • 当ViewGroup决定拦截事件的时候,那么后续的点击事件将默认交给他,不再调用onInterceptTouchEvent
  • FLAG_DISALLOW_INTERCEPT作用是让ViewGroup不再拦截事件,前提是ViewGroup不拦截Action_Down事件
  • onInterceptTouchEvent不是每次都会调用的,如果我们要提前处理点击事件需要在dispatchTouchEvent
  • 当我们遇到滑动冲突的时候,可以考虑FLAG_DISALLOW_INTERCEPT来处理

我们看一下ViewGroup不拦截的事件的情况

先看一下源码,这个是删减后的源码,看起来比较清楚

            //如果不拦截,并且不是cancel事件,就进行遍历子view分发事件
            if (!canceled && !intercepted) {

            ...

                //当ACTION_DOWN和ACTION_POINTER_DOWN和ACTION_HOVER_MOVE时候才会遍历子view
                if (actionMasked == MotionEvent.ACTION_DOWN
                        || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                        || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
                  
                        //找到可以接受触摸事件孩子,从前向后遍历查找
                        final View[] children = mChildren;
                        for (int i = childrenCount - 1; i >= 0; i--) {
                            final int childIndex = getAndVerifyPreorderedIndex(
                                    childrenCount, i, customOrder);
                            final View child = getAndVerifyPreorderedView(
                                    preorderedList, children, childIndex);

                     	 ...
                      
                            //判断触摸点是否在此View的范围中,是否在移动
                            if (!canViewReceivePointerEvents(child)
                                    || !isTransformedTouchPointInView(x, y, child, null)) {
                                ev.setTargetAccessibilityFocus(false);
                                continue;
                            }

           					...
           					
                            //分发事件,如果事件被子view消费,就跳出循环,不再继续分发给其他view
                            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                            
                               ...
                               
                                //addTouchTarget内部赋值mFirstTouchTarget=当前view
                                newTouchTarget = addTouchTarget(child, idBitsToAssign);
                                alreadyDispatchedToNewTouchTarget = true;
                                break;
                            }
            }

首先遍历ViewGroup的所有子元素,然后判断判断子元素是否能接收到点击事件,是否能接收到点击事件主要由俩点来衡量

  • 点击的坐标是否落在了子元素的区域内
  • 子元素是否在播放动画

如果子元素满足这俩个条件,那么事件将传递给他处理,分发事件其实dispatchTransformedTouchEvent是这个方法做的,我们看一下dispatchTransformedTouchEvent源码

 private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits) {
        final boolean handled;
		//先记住这一段判断cancel的源码,很重要下面分析
        if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
            event.setAction(MotionEvent.ACTION_CANCEL);
            if (child == null) {
                handled = super.dispatchTouchEvent(event);
            } else {
                handled = child.dispatchTouchEvent(event);
            }
            event.setAction(oldAction);
            return handled;
        }

     ....
     
                if (child == null) {
                    handled = super.dispatchTouchEvent(event);
                } else {
               		...
                    handled = child.dispatchTouchEvent(event);
                }
                
			.....
			
        return handled;
    }

这里面主要代码如果 if (cancel || oldAction == MotionEvent.ACTION_CANCEL) 为false,这个判断的意思是,如果不是ACTION_CANCEL,外部传入的cancel也为fasle,就进行下面的判断,而下面的判断主要是根据传入的child是否为null来判断的,如果child不为null,那么就调用child的dispatchTouchEvent方法,这个事件就交给子元素去处理,这就完成一轮的事件分发

如果child的dispatchTouchEvent返回为true,先不考虑事件怎么在子元素中分发,那么mFirstTouchTarget就被赋值,跳出for循环

    //分发事件,如果事件被子view消费,就跳出循环,不再继续分发给其他view
                            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                            
                               ...
                               
                                //addTouchTarget内部赋值mFirstTouchTarget=当前view
                                newTouchTarget = addTouchTarget(child, idBitsToAssign);
                                alreadyDispatchedToNewTouchTarget = true;
                                break;
                            }

上面的代码完成了,给mFirstTouchTarget赋值,并且跳出for循环,终止对子元素的遍历,如果子元素的dispatchTouchEvent返回fasle,那么就会继续遍历子元素,把事件传递给下一个合适的子元素(如果还有合适的子元素的话)

mFirstTouchTarget赋值是在addTouchTarget方法内部完成的,mFirstTouchTarget是一个单链表结构

  private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
        final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
        //注意这里这里很重要,target.next =null,然后 mFirstTouchTarget = target;也就是说这时候的 mFirstTouchTarget.next=null
        target.next = mFirstTouchTarget;
        mFirstTouchTarget = target;
        return target;
    }

如果遍历所有的子元素事件都没有合适的处理,这里包含俩种情况,一种就是ViewGroup没有子元素,第二种就是子元素的dispatchTouchEvent返回了fasle,这俩种情况下ViewGroup会自己处理事件

       //遍历完所有的子View后,还没有处理事件,就自己处理
            if (mFirstTouchTarget == null) {
                handled = dispatchTransformedTouchEvent(ev, canceled, null,
                        TouchTarget.ALL_POINTER_IDS);
            }

注意这里child参数传入的是null,根据之前的分析就会调用 super.dispatchTouchEvent(event);由于ViewGroup也是继承自View,这里就会转到View的dispatchTouchEvent,即点击事件交给View处理

注意敲黑板了啊

我看了很多博客,都没有对这种情况进行分析,这个问题一度卡了我很久

现在考虑一种情况,如果父View的onInterceptTouchEvent的Down事件返回false不拦截,move up事件返回true拦截,这个效果就是子View只能收到Down事件而收不到Up和Move事件

那么我们现在分析一下这种情况,按照我们上方的分析,父View的Down事件不拦截,那么mFirstTouchTarget就会被赋值,第二次Move和Up事件要拦截,但是由于mFirstTouchTarget被赋值了,所以是走不到下面这步的

    // //遍历完所有的子View后,还没有处理事件,就自己处理
            if (mFirstTouchTarget == null) {
                // No touch targets so treat this as an ordinary view.
                handled = dispatchTransformedTouchEvent(ev, canceled, null,
                        TouchTarget.ALL_POINTER_IDS);
            }

那么父View是怎么拦截Move和Up事件的呢? 当地一个Move事件传递给父View后,此时mFirstTouchTarget不为null,所以走拦截这一步代码

		//如果是ACTION_DOWN,或者mFirstTouchTarget != null,就进行拦截验证
            final boolean intercepted;
            if (actionMasked == MotionEvent.ACTION_DOW || 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;
            }

拦截返回true后,不走遍历子Vew代码,直接到最后的判断代码

    // //遍历完所有的子View后,还没有处理事件,就自己处理
            if (mFirstTouchTarget == null) {
                // No touch targets so treat this as an ordinary view.
                handled = dispatchTransformedTouchEvent(ev, canceled, null,
                        TouchTarget.ALL_POINTER_IDS);
            } else {
                //Action_Down之外的事件直接分发给目标view
                TouchTarget predecessor = null;
                TouchTarget target = mFirstTouchTarget;
                while (target != null) {
                    final TouchTarget next = target.next;
                    //如果上方遍历已经传递过改事件,则跳过本次事件
                    if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                        handled = true;
                    } else {
                        final boolean cancelChild = resetCancelNextUpFlag(target.child)
                                || intercepted;
                        if (dispatchTransformedTouchEvent(ev, cancelChild,
                                target.child, target.pointerIdBits)) {
                            handled = true;
                        }
                          if (cancelChild) {
                            if (predecessor == null) {
                                mFirstTouchTarget = next;
                            } else {
                                predecessor.next = next;
                            }
                            target.recycle();
                            target = next;
                            continue;
                        }
                }
            }

由于mFirstTouchTarget在Down的时候已经赋值不为null,会走下边代码

       final boolean cancelChild = resetCancelNextUpFlag(target.child)
                                || intercepted;

由于拦截事件,cancelChild为true,也就是说下面这个分发dispatchTransformedTouchEvent的方法传入的是true

     if (dispatchTransformedTouchEvent(ev, cancelChild,
                                target.child, target.pointerIdBits)) {
                            handled = true;
                        }

在这个分发方法里,有判断Cancel事件的代码

 private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits) {
        final boolean handled;
		//先记住这一段判断cancel的源码,很重要下面分析
        if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
            event.setAction(MotionEvent.ACTION_CANCEL);
            if (child == null) {
                handled = super.dispatchTouchEvent(event);
            } else {
                handled = child.dispatchTouchEvent(event);
            }
            event.setAction(oldAction);
            return handled;
        }
        ...
			
        return handled;
    }

由于传入的cancel为true, 会重新定义事件为Cancel事件event.setAction(MotionEvent.ACTION_CANCEL);child不为null所以会调用child.dispatchTouchEvent(event);也就是说第一个Move事件,父View不会拦截,但会给子View发送一个Cancel事件

接下来会继续走代码

  TouchTarget target = mFirstTouchTarget;
  final TouchTarget next = target.next;
...

         if (cancelChild) {
             ...
                mFirstTouchTarget = next;
			...
                        }

上面已经分析过cancelChild为true,进入方法给mFirstTouchTarget重新赋值mFirstTouchTarget.next,那么mFirstTouchTarget.next等于什么?看下面一段代码

  private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
        final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
        //注意这里这里很重要,target.next =null,然后 mFirstTouchTarget = target;也就是说这时候的 mFirstTouchTarget.next=null
        target.next = mFirstTouchTarget;
        mFirstTouchTarget = target;
        return target;
    }

其实mFirstTouchTarget.next=null,那整合起来就是把mFirstTouchTarget重新赋值为null,从这里开始,第二个Move事件就会直接传递给父View完成了拦截

    if (mFirstTouchTarget == null) {
                handled = dispatchTransformedTouchEvent(ev, canceled, null,
                        TouchTarget.ALL_POINTER_IDS);
            } 

总结

当父View不拦截Down事件,但要拦截Move和Up事件时,第一个Move事件会重新赋值为Cancel事件发送给子View,然后mFirstTouchTarget赋值为null,第二次开始的Move事件就会交给父View

View的事件分发源码

View对事件的处理比较简单,注意这里的View不包括ViewGroup,先看他的dispatchTouchEvent

 public boolean dispatchTouchEvent(MotionEvent event) {
 
     ...

        boolean result = false;
            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;
            }
        }
	...
        return result;
    }

View的时间传递比较简单,因为View(不包括ViewGroup),是一个单独的元素,无法向下传递事件,所以没有onInterceptTouchEvent方法,从上面源码可以看出

  • 首先会判断与没有mOnTouchListener,如果有并且其中的onTouch方法返回true那么onTouchEvent放方法不会调用,可以看出mOnTouchListener的优先级高于onTouchEvent

下面看一下onTouchEvent方法的源码

首先看一下,当View处于不可用状态下,事件的处理过程

   final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
                || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
                || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;

        if ((viewFlags & ENABLED_MASK) == DISABLED) {
            if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
                setPressed(false);
            }
            mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
            // A disabled view that is clickable still consumes the touch
            // events, it just doesn't respond to them.
            return clickable;
        }

可以看出不可用的状态下,View消耗点击事件

再看一下对具体事件的处理

   final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
                || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
                || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
                
        if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
            switch (action) {
                case MotionEvent.ACTION_UP:
                ...

                                if (mPerformClick == null) {
                                    mPerformClick = new PerformClick();
                                }
                                if (!post(mPerformClick)) {
                                    performClickInternal();
                                }
                            }
                        }
                        
                        ....

                case MotionEvent.ACTION_DOWN:
                  ...

                case MotionEvent.ACTION_CANCEL:
                ...

                case MotionEvent.ACTION_MOVE:
                    ...
                    break;
            }

从上面代码为可以看出

  • 只要View的CLICKABLE和LONG_CLICKABLE一个为true,不管他是不是DISABLED状态都消耗事件,只不过DISABLED不走下面的down,up事件
  • 当Action_Up触发时,会调用PerformClick方法,如果View设置了onClickListener,那么PerformClick将调用他的onClick方法
  • View的LONG_CLICKABLE默认是false,但是CLICKABLE是否为fasle,跟具体View有关,可点击的CLICKABLE为true,不可点击的CLICKABLE为false
  • setClickable和setLongClickable可以改变CLICKABLE,和LONG_CLICKABLE的值
  • setClickLinsterer和setLongClickLinsterer会自动设置CLICKABLE和LONG_CLICKABLE为true

到这里事件分发就处理完了

参考:Android开发艺术探索

allenfeng.com/2017/02/22/…