View事件分发

一个点击或触摸事件会被内部封装成MotionEvent对象。而事件分发就是将MotionEvent往子View传递。有View的地方就有Window,View必须依附于Window进行展示。我们通过setContentView()设置的布局,会被添加到DecorView中,DecorView会被添加到Window,而Window则被加到Activity中,这里用一张图展示它们的层次关系。


image.png

事件的分发会经历3个方法,往下递归调用,分别是dispatchTouchEvent、onInterceptTouchEvent和onTouchEvent方法。

从子View的角度来说,dispatchTouchEvent可以理解为接收到事件的意思,也就是当父View要把事件发给我时,会调用dispatchTouchEvent来告知我接收这个事件,所以Activity、ViewGroup和View都有这个方法。

onInterceptTouchEvent是把事件拦截了的意思,Activity是下发的源头,不能还没发出去就把事件拦截掉,而View不是容器,是没有子View的,只有消费和不消费事件一说,没有必要拦截,所以,Activity和View都不会有onInterceptTouchEvent这个方法。

最后一个onTouchEvent是用来决定是否要消费事件的,当然消费事件不止它一个,它的优先级也不是最高的,还有设置触摸监听的setOnTouchListener和点击事件onClick方法。

这里假设一个最简单的场景,ContentView里有一个ViewGroup,ViewGroup里放了一个View。下面通过源码一步步分析,但手指按下时,Activity 会首先收到一个MotionEvent事件,它为ACTION_DOWN类型的。

public class Activity extends ContextThemeWrapper
        implements LayoutInflater.Factory2,
        Window.Callback, KeyEvent.Callback,
        OnCreateContextMenuListener, ComponentCallbacks2,
        Window.OnWindowDismissedCallback {

    //事件分发源头
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            onUserInteraction();
        }
        //通过Window下发
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        //自己处理
        return onTouchEvent(ev);
    }

    //空实现
    public void onUserInteraction() {
    }
}

Window是Activity的下一层,而Activity接到事件后,并没做什么处理,而是将事件交给Window去做分发。如果superDispatchTouchEvent返回了true,即说明这个事件被某个控件消费了,流程结束。如果返回false,说明这个事件没有控件消费,则调用自己的onTouchEvent把事件消费掉。

接下来看Window如何将事件传递给ViewGroup。

public abstract class Window {
  
  public abstract boolean superDispatchTouchEvent(MotionEvent event);
 
}

Window 是一个抽象类,superDispatchTouchEvent也是一个抽象方法,它的唯一实现类为PhoneWindow。

public class PhoneWindow extends Window implements MenuBuilder.Callback {
  private DecorView mDecor;
   @Override
    public boolean superDispatchTouchEvent(MotionEvent event) {
        return mDecor.superDispatchTouchEvent(event);
    }
}

PhoneWindow也什么都没做,将事件交给Window去做分发。

public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {

  public boolean superDispatchTouchEvent(MotionEvent event) {
        return super.dispatchTouchEvent(event);
    }

}

前面的调用方法都是superDispatchTouchEvent,而不是dispatchTouchEvent,而这里我们第一次看到dispatchTouchEvent的调用,也就是说,从此处,事件开始向下一层层分发给子View了。

而DecorView 继承自FrameLayout ,我们知道FrameLayout 是一个容器,即继承了ViewGroup,而且FrameLayout 也没有重写dispatchTouchEvent方法。

public class FrameLayout extends ViewGroup {
}

因此,事件将通过ViewGroup来处理。上面提到过,此时我们可以认为,DecorView是第一个接到事件的ViewGroup。由于dispatchTouchEvent方法比较长,我们一段段分析。

public abstract class ViewGroup extends View implements ViewParent, ViewManager {
  
    protected int mGroupFlags;
  
    private TouchTarget mFirstTouchTarget;

    //DecorView开始事件分发
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
        }
        //事件是否被处理了
        boolean handled = false;

        if (onFilterTouchEventForSecurity(ev)) {
            final int action = ev.getAction();
            final int actionMasked = action & MotionEvent.ACTION_MASK;

           //每次ACTION_DOWN都重新开始分发的流程,所以清理掉之前的事件
            if (actionMasked == MotionEvent.ACTION_DOWN) {
               //清除之前的所有事件
                cancelAndClearTouchTargets(ev);
                //重置
                resetTouchState();
            }
            
            //检查是否要拦截
            final boolean intercepted;
            //当不是Down事件时:
            // 如果子View消费了事件,mFirstTouchTarget不为null,
            // mFirstTouchTarget为null,说明子View都没有消费,直接跳else执行拦截
            if (actionMasked == MotionEvent.ACTION_DOWN|| mFirstTouchTarget != null) {
                //是否拦截
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                //不拦截进入判断
                if (!disallowIntercept) {
                    //如果重写onInterceptTouchEvent返回true,此处intercepted = true进行拦截
                    intercepted = onInterceptTouchEvent(ev);//默认为false
                    //恢复事件防止其改变
                    ev.setAction(action); 
                } else {
                    //不拦截
                    intercepted = false;
                }
            } else {
                //子控件没有消费DOWN事件
                intercepted = true;
            }

这里有几个关键的变量,intercepted用来控制事件是否要继续往下分发,初始值为false,即不拦截。正常情况下,当按下手指时,第一个事件会为ACTION_DOWN,即actionMasked是ACTION_DOWN。mFirstTouchTarget ,见名知意,它表示第一个事件由谁消费,但不包括自己,意思是,如果有子控件消费了down事件,那么mFirstTouchTarget 就会指向那个对象,不为null,如果没有子控件消费down事件,则为null。

那么当用户按下手指饼滑动时,第二个传递过来的事件则为ACTION_MOVE,actionMasked == MotionEvent.ACTION_DOWN不成立,如果刚才的ACTION_DOWN事件子控件没有消费,mFirstTouchTarget !=null也不成立,将进入else判断,intercepted 置为true。

也就是说,子控件一开始没有消费ACTION_DOWN事件的话,那则往后的事件都默认拦截,不会往下发了。同时也不会再执行到onInterceptTouchEvent这个方法。


   private void resetTouchState() {
        clearTouchTargets();
        resetCancelNextUpFlag(this);
        //重置为0,即false
        mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
        mNestedScrollAxes = SCROLL_AXIS_NONE;
    }

  //请求父控件不拦截事件
   public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
        //如果子控件请求的和当前状态相同,无需往下执行
        if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) {
           return;
        }
        //是否拦截
        if (disallowIntercept) {
            mGroupFlags |= FLAG_DISALLOW_INTERCEPT;
        } else {
            mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
        }
        //有父类继续告知
        if (mParent != null) {
            mParent.requestDisallowInterceptTouchEvent(disallowIntercept);
        }
    }

FLAG_DISALLOW_INTERCEPT是一个标志位,它可以通过requestDisallowInterceptTouchEvent来设置是否进行拦截方法的调用,它一般用于子View来请求父控件拦截或不拦截事件,但只能作用于非ACTION_DOWN的情况下。因为每次ACTION_DOWN都是一轮分发的重新开始,也就是说之前的事件都废弃了。resetTouchState会重置状态,包括FLAG_DISALLOW_INTERCEPT这个标志位。

所以,接到ACTION_DOWN事件时,总会执行到onInterceptTouchEvent方法。

public boolean onInterceptTouchEvent(MotionEvent ev) {
        return false;
    }

这个方法很简单,如果子类不重写的话默认返回false,不拦截。

            //检查事件是否取消
            final boolean canceled = resetCancelNextUpFlag(this)
                    || actionMasked == MotionEvent.ACTION_CANCEL;

            //如果有必要,为DOWN事件检查所有的目标对象
            final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;

            TouchTarget newTouchTarget = null;//链表节点
            boolean alreadyDispatchedToNewTouchTarget = false;
            //事件没有取消,同时没有拦截,则往下分发
            if (!canceled && !intercepted) {
                //如果事件为起始事件
                if (actionMasked == MotionEvent.ACTION_DOWN
                        || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                        || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
                    final int actionIndex = ev.getActionIndex(); 
                    final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
                            : TouchTarget.ALL_POINTER_IDS;

                   
                    removePointersFromTouchTargets(idBitsToAssign);

                    final int childrenCount = mChildrenCount;
                    //newTouchTarget为null,并且有子View
                    if (newTouchTarget == null && childrenCount != 0) {
                        final float x = ev.getX(actionIndex);
                        final float y = ev.getY(actionIndex);
                      
                        final ArrayList preorderedList = buildOrderedChildList();
                        final boolean customOrder = preorderedList == null && isChildrenDrawingOrderEnabled();
                        final View[] children = mChildren;
                        //倒序寻找是否有子View处理此事件
                        for (int i = childrenCount - 1; i >= 0; i--) {
                            final int childIndex = customOrder ? getChildDrawingOrder(childrenCount, i) : i;
                            final View child = (preorderedList == null)? children[childIndex] : preorderedList.get(childIndex);

                            //如果子View有动画在执行,或者没有落在这个View的范围内,就继续找下一个子View
                            if (!canViewReceivePointerEvents(child)|| !isTransformedTouchPointInView(x, y, child, null)) {
                                continue;
                            }

                            //将接收事件的子View赋值给newTouchTarget 
                            newTouchTarget = getTouchTarget(child);
                            if (newTouchTarget != null) {
                                   newTouchTarget.pointerIdBits |= idBitsToAssign;
                                break;
                            }

                            resetCancelNextUpFlag(child);
                            //1,如果子View是ViewGroup,会递归调用此dispatchTouchEvent往下分发,如果返回某个View返回true,则说明被消费了,否则没有消费,继续下发,直到最后一个是View,即2的情况
                            //2,如果子View不包含子View了,dispatchTransformedTouchEvent会调用View的dispatchTouchEvent方法,进而调用onTouchEvent如果返回true,说明被消费了,否则没有消费
                            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                              
                                 ......



  //是否在执行动画
   private static boolean canViewReceivePointerEvents(View child) {
        return (child.mViewFlags & VISIBILITY_MASK) == VISIBLE
                || child.getAnimation() != null;
    }

此时如果事件没有被取消,则会遍历所有的子控件,看哪一个能接收这个事件,条件有两个,即控件是否有动画在执行,和是否落在触摸的区域。getTouchTarget这个方法用于查找这个View是否在当前的ViewGroup中。

private TouchTarget getTouchTarget(View child) {
        for (TouchTarget target = mFirstTouchTarget; target != null; target = target.next) {
            if (target.child == child) {
                return target;
            }
        }
        return null;
    }

当View被添加到ViewGroup中时,会通过链表结构,将View封装成一个TouchTarget的链表节点,这里是遍历链表,如果找到了,返回这个TouchTarget节点,并跳出上面的遍历循环。

此时,流程执行到dispatchTransformedTouchEvent方法,它是处理事件继续向下分发的关键方法。

    //传递到子View的事件
    private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits) {
        //是否消费了事件
        final boolean handled;

        final int oldAction = event.getAction();
        //取消事件
        if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
            event.setAction(MotionEvent.ACTION_CANCEL);
            //没有子View消费事件,看自己消不消费
            if (child == null) {
                //直接调用父类的dispatchTouchEvent
                handled = super.dispatchTouchEvent(event);
            } else {
                //如果有子View,将cancle事件传递到子View中
                handled = child.dispatchTouchEvent(event);
            }
            event.setAction(oldAction);
            return handled;
        }
       
        //计算即将被传递的点的数量
        final int oldPointerIdBits = event.getPointerIdBits();
        final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;

        //如果事件没有相应的点,那么丢弃该事件
        if (newPointerIdBits == 0) {
            return false;
        }

        //保存坐标转换后的MotionEvent
        final MotionEvent transformedEvent;
        //如果事件点的数量一致
        if (newPointerIdBits == oldPointerIdBits) {
            //子元素为空,或有一个单位矩阵
            if (child == null || child.hasIdentityMatrix()) {
                //空的情况
                if (child == null) {
                    //调用父类的dispatchTouchEvent
                    handled = super.dispatchTouchEvent(event);
                } else {
                    //获取xy方向的偏移量(使用scrollTo或scrollBy滚动)
                    final float offsetX = mScrollX - child.mLeft;
                    final float offsetY = mScrollY - child.mTop;
                    //将MotionEvent进行坐标变换
                    event.offsetLocation(offsetX, offsetY);

                    //变换后的MotionEvent传给子View
                    handled = child.dispatchTouchEvent(event);

                    //复位MotionEvent以便之后再次使用
                    event.offsetLocation(-offsetX, -offsetY);
                }
                //返回是否消费
                return handled;
            }
            transformedEvent = MotionEvent.obtain(event);
        } else {
            transformedEvent = event.split(newPointerIdBits);
        }

      
        if (child == null) {
            //如果没有包含子view了,则使用父类View的dispatchTouchEvent
            handled = super.dispatchTouchEvent(transformedEvent);
        } else {
            final float offsetX = mScrollX - child.mLeft;
            final float offsetY = mScrollY - child.mTop;
            transformedEvent.offsetLocation(offsetX, offsetY);
            if (! child.hasIdentityMatrix()) {
                transformedEvent.transform(child.getInverseMatrix());
            }
            //有子View,则调用子View的dispatchTouchEvent往下分发
            handled = child.dispatchTouchEvent(transformedEvent);
        }
        
        transformedEvent.recycle();
        return handled;
    }

这里先对父类/子类和父View/子View名词做下强调,前一组指的是继承关系,后一组是包含关系。

  • 如果上面遍历找到接收的子View不为null,则调用子View的dispatchTouchEvent方法,将事件传递给子View。
  • 如果子View为null,事件不再往下传递,调用父类,即View的dispatchTouchEvent方法。

我们知道,控件要么是ViewGroup,要么是View。这意味着,无论是ViewGroup还是View,如果进入了View类的dispatchTouchEvent方法,也就开始是否消费事件的流程了。

我们回头再分析View的dispatchTouchEvent方法。先假设handled最后返回了true,则dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)判断满足。

                  ......

                  if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                               //子View接收到事件的时间戳
                                mLastTouchDownTime = ev.getDownTime();
                                if (preorderedList != null) {
                                 //找到在列表中的角标
                                    for (int j = 0; j < childrenCount; j++) {
                                        if (children[childIndex] == mChildren[j]) {
                                            mLastTouchDownIndex = j;
                                            break;
                                        }
                                    }
                                } else {
                                    mLastTouchDownIndex = childIndex;
                                }
                                mLastTouchDownX = ev.getX();
                                mLastTouchDownY = ev.getY();
                                //如果子view消费了,把消费了事件的View对应的消费节点,赋值给newTouchTarget
                                newTouchTarget = addTouchTarget(child, idBitsToAssign);
                                alreadyDispatchedToNewTouchTarget = true;
                                break;
                            }
                        }
                        if (preorderedList != null) preorderedList.clear();
                       }

                        //如果此时触发没有找到接收的View,将最近一次的目标引用,作为当前事件的的目标引用
                    if (newTouchTarget == null && mFirstTouchTarget != null) {
                        
                        newTouchTarget = mFirstTouchTarget;
                        while (newTouchTarget.next != null) {
                            newTouchTarget = newTouchTarget.next;
                        }
                        newTouchTarget.pointerIdBits |= idBitsToAssign;
                    }
                }
            }
                    }

接收ACTION_DOWN事件的控件找到了,于是,通过addTouchTarget方法,一开始定义的mFirstTouchTarget变量便被赋值,指向对应的控件,并终止对子View的遍历。如果上面的handled返回了false,而且还有子View的话,将继续遍历寻找。

 private TouchTarget addTouchTarget(View child, int pointerIdBits) {
        //将目标控件构建成一个TouchTarget 节点
        TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
        target.next = mFirstTouchTarget;
        //赋值给mFirstTouchTarget 
        mFirstTouchTarget = target;
        return target;
    }

如果找寻到最后,mFirstTouchTarget 仍旧为null,回看到我们一开始的分析,intercepted将置为ture,同一轮中后面所有的事件都被默认拦截了。也不会再遍历包含的子View了。

                        ......

                        newTouchTarget.pointerIdBits |= idBitsToAssign;
                    }
                }
            }
            //没有子View消费此事件
            if (mFirstTouchTarget == null) {
                //child为null,会执行View的dispatchTouchEvent
                handled = dispatchTransformedTouchEvent(ev, canceled, null,  TouchTarget.ALL_POINTER_IDS);
            } else {
               //往后的事件都分发给mFirstTouchTarget 
                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;
                        //mFirstTouchTarget 消费事件
                        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;
                        }
                    }
                    predecessor = target;
                    target = next;
                }
            }
            
            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;
    }

这里的代码非常关键,无论mFirstTouchTarget 是不是null,后面的事件都不会再遍历找寻子View。

  • 当mFirstTouchTarget 不为null时,后面的事件都直接交给mFirstTouchTarget这个控件。

  • 当mFirstTouchTarget 为null时,事件将由自己做消费判断。

出现第二种情形包含3种可能:

  • ViewGroup没有子View。
  • ViewGroup所有的子View都没有落在触摸的范围。
  • 子View的dispatchTouchEvent返回false,一般在onTouchEvent中返回。

相同的是,最终都会调用dispatchTransformedTouchEvent方法,但传入的参数不同,最主要是第3个参数,一个传入null,表示自己消费,一个传入target.child,目标控件消费。但最终都进入View的dispatchTouchEvent方法。

public boolean dispatchTouchEvent(MotionEvent event) {
        boolean result = false;

        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onTouchEvent(event, 0);
        }

        final int actionMasked = event.getActionMasked();
        if (actionMasked == MotionEvent.ACTION_DOWN) {
            stopNestedScroll();
        }

        if (onFilterTouchEventForSecurity(event)) {
          //ListenerInfo封装了手势监听,触摸监听等监听接口
            ListenerInfo li = mListenerInfo;
            
            //设置的OnTouchListener
            if (li != null && li.mOnTouchListener != null
                    && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) {
                result = true;
            }
            //如果上面result不为true,执行onTouchEvent
            if (!result && onTouchEvent(event)) {
                result = true;
            }
        }

        if (!result && mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
        }
        
        if (actionMasked == MotionEvent.ACTION_UP ||
                actionMasked == MotionEvent.ACTION_CANCEL ||
                (actionMasked == MotionEvent.ACTION_DOWN && !result)) {
            stopNestedScroll();
        }

        return result;
    }

如源码所示,OnTouchListener优先级大于onTouchEvent,而且随后的分析将看到,onClick是在onTouchEvent中被调用的,所以,如果setOnTouchListener的onTouch返回true,那么onTouchEvent方法不会执行,setOnClickListener的onClick回调也不会执行。

我们依旧假设OnTouchListener返回false,事件进入onTouchEvent方法。

  public boolean onTouchEvent(MotionEvent event) {
        final float x = event.getX();
        final float y = event.getY();
        final int viewFlags = mViewFlags;

        //不可用状态也消费
        if ((viewFlags & ENABLED_MASK) == DISABLED) {
            if (event.getAction() == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
                setPressed(false);
            }
            //只要可点击就返回true
            return (((viewFlags & CLICKABLE) == CLICKABLE ||
                    (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));
        }

        //设置了代理
        if (mTouchDelegate != null) {
            if (mTouchDelegate.onTouchEvent(event)) {
                return true;
            }
        }

        //注意if作用域结束后返回true
        if (((viewFlags & CLICKABLE) == CLICKABLE ||
                (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
            switch (event.getAction()) {
                case MotionEvent.ACTION_UP:
                    boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
                    if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
                        
                        boolean focusTaken = false;
                        if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
                            focusTaken = requestFocus();
                        }

                        if (prepressed) {
                              setPressed(true, x, y);
                       }

                        if (!mHasPerformedLongPress) {//长按则不响应点击事件
                            
                            removeLongPressCallback();
                      
                            if (!focusTaken) {
                                
                                if (mPerformClick == null) {
                                    mPerformClick = new PerformClick();
                                }

                                //点击事件进入队列执行
                                if (!post(mPerformClick)) {
                                    performClick();
                                }
                            }
                        }

                        if (mUnsetPressedState == null) {
                            mUnsetPressedState = new UnsetPressedState();
                        }

                        if (prepressed) {
                            postDelayed(mUnsetPressedState,
                                    ViewConfiguration.getPressedStateDuration());
                        } else if (!post(mUnsetPressedState)) {
                            
                            mUnsetPressedState.run();
                        }
                        removeTapCallback();
                    }
                    break;
                //按下
                case MotionEvent.ACTION_DOWN:
                  //是否长按
                    mHasPerformedLongPress = false;

                    if (performButtonActionOnTouchDown(event)) {
                        break;
                    }
              
                    boolean isInScrollingContainer = isInScrollingContainer();

                    if (isInScrollingContainer) {
                        mPrivateFlags |= PFLAG_PREPRESSED;
                        if (mPendingCheckForTap == null) {
                            mPendingCheckForTap = new CheckForTap();
                        }
                        mPendingCheckForTap.x = event.getX();
                        mPendingCheckForTap.y = event.getY();
                        postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
                    } else {
                        //状态改为按下,比如button这时会变背景,需要刷新ui
                        setPressed(true, x, y);
                        checkForLongClick(0);//检查并保存是否长按状态
                    }
                    break;

                case MotionEvent.ACTION_CANCEL:
                    setPressed(false);
                    removeTapCallback();
                    removeLongPressCallback();
                    break;

                case MotionEvent.ACTION_MOVE:
                    drawableHotspotChanged(x, y);
                   //是否超出View的范围
                    if (!pointInView(x, y, mTouchSlop)) {
     
                        removeTapCallback();
                        if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
                             //移除长按的监听
                            removeLongPressCallback();
                            //不是按下状态
                            setPressed(false);
                        }
                    }
                    break;
            }
            return true;
        }
        return false;
    }

我们可以得出这样的结论:View的enable属性并不影响事件的消费,只要CLICKABLE和LONG_CLICKABLE有一个为true,onTouchEvent就会返回ture,将事件消费。

ACTION_UP事件发生时,如果外部进行setClickListener,将触发performClick(),回调onClick方法。

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

        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
        return result;
    }

View的LONG_CLICKABLE默认为false,不可点击的View,CLICKABLE默认为false,比如TextView,可点击的View,CLICKABLE则为true,比如Button。但是,我们知道View有监听点击的方法,setOnLongClickListener会将LONG_CLICKABLE置为true,setOnClickListener将CLICKABLE置为true。

public void setOnLongClickListener(OnLongClickListener l) {
        if (!isLongClickable()) {
            setLongClickable(true);
        }
        getListenerInfo().mOnLongClickListener = l;
    }


 public void setOnClickListener(OnClickListener l) {
        if (!isClickable()) {
            setClickable(true);
        }
        getListenerInfo().mOnClickListener = l;
    }

到这里,整个事件分发的流程分析完毕。事件回调优先级顺序也清晰了:

onTouch > onTouchEvent > onClick

你可能感兴趣的:(View事件分发)