view-源码角度理解Android事件分发

前言

对于Android的事件分发,很早之前是看的图解 Android 事件分发机制,其中的事件分发的U型图很好的解释了分发的过程。理解起来就类似领导给下属分发任务,任务从上到下分发,下属能完成就处理掉任务,不能处理然后返回给领导,让领导处理。

对于事件传递主要涉及3个方法,Android底层监听到触摸产生事件,事件一路传递到Activity,这一过程参考Android触摸事件全过程分析:由产生到Activity.dispatchTouchEvent(),最终activity接受到事件后调用其dispatchTouchEvent方法开始事件分发

  • dispatchTouchEvent 此方法三个层级都有,在View 层级中主要是处理事件,在其他两个层级主要是事件分发
  • onInterceptTouchEvent 此方法只有在ViewGroup中存在,用于事件拦截,拦截后的事件交给ViewGroup的onTouchEvent
  • onTouchEvent此方法主要用于事件处理,ViewGroup没有实现onTouchEvent,使用父类的View的onTouchEvent
dispatchTouchEventonInterceptTouchEventonTouchEvent
Activity
ViewGroup
View

事件分发U型图

在分析源码前还是放一张图解 Android 事件分发机制中的经典图,对于不太熟悉事件怎么传递的可以先熟悉这张图,然后带着这张图的流程去看源码。
在这里插入图片描述

Activity

下面源码来自Activity,前面说了Android底层监听到触摸产生事件,事件一路传递到Activity,最终会调用Activity的dispatchTouchEvent方法。而在dispatchTouchEvent方法里面的if (getWindow().superDispatchTouchEvent(ev))判断将事件向下传递。
getWindow()获取的是Window的唯一实现类PhoneWindow,在PhoneWindow中调用mDecor.superDispatchTouchEvent(event),mDecor其实是DecorView,DecorView继承FrameLayout继承ViewGroup,在DecorView中调用的super.dispatchTouchEvent(event)其实是ViewGroup的方法,这里就将Activity的事件传递到了ViewGroup。

我们继续看Activity的方法,在Activity中对事件的处理取决于关键代码getWindow().superDispatchTouchEvent(ev)

  1. 判断getWindow().superDispatchTouchEvent(ev),代码回调到ViewGroup::dispatchTouchEvent方法,
  2. 如果ViewGroup::dispatchTouchEvent返回true,那么Activity的dispatchTouchEvent返回true,不走自身onTouchEvent,
  3. 如果ViewGroup::dispatchTouchEvent返回false,那么Activity调用自身onTouchEvent并得到onTouchEvent的返回值
  4. 如果在自己Activity中直接重写dispatchTouchEvent()方法,直接返回true或者false,那么就不会分发到ViewGroup,也不会执行Activity的onTouchEvent(),只有在自己Activity中不实现dispatchTouchEvent(),或者返回super.dispatchTouchEvent(ev)那么才有可能向下传递。

上面一大段的判断其实就是看getWindow().superDispatchTouchEvent(ev)返回什么,你看完这个再对照U型图,看看是不是一样的

dispatchTouchEvent

public class Activity extends ContextThemeWrapper
        implements LayoutInflater.Factory2,
        Window.Callback, KeyEvent.Callback,
        OnCreateContextMenuListener, ComponentCallbacks2,
        Window.OnWindowDismissedCallback, WindowControllerCallback,
        AutofillManager.AutofillClient {
        ...
        
	 public boolean dispatchTouchEvent(MotionEvent ev) {
 
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        return onTouchEvent(ev);
    }
   
}    

onTouchEvent

	 ...
	  public boolean onTouchEvent(MotionEvent event) {
        if (mWindow.shouldCloseOnTouch(this, event)) {
            finish();
            return true;
        }

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

到这里就将Activity的事件传递到ViewGroup

public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {
...
	  public boolean superDispatchTouchEvent(MotionEvent event) {
        return super.dispatchTouchEvent(event);
    }
...
}

ViewGroup

在ViewGroup中重写了dispatchTouchEvent方法,但是onTouchEvent是复用View的。
当activity中将事件传递到了ViewGroup,在ViewGroup中首先由dispatchTouchEvent用于事件分发。ViewGroup的dispatchTouchEvent()方法非常的长,下面我会做3个部分讲解
首先在MotionEvent中我们知道有几种触摸状态

  1. ACTION_DOWN:按下的状态
  2. ACTION_MOVE:移动的时候状态
  3. ACTION_UP:抬起的时候状态

虽然源码很长,但是大部分源码都在if (onFilterTouchEventForSecurity(ev))判断里面,我将判断里面的源码分为3个部分

判断是否拦截

第一部分源码:这一部分主要是判断事件有没有拦截和取消。在下面这段源码里面,会得到3个属性

  1. 调用intercepted = onInterceptTouchEvent(ev),判断是否有拦截
  2. 得到canceled是否取消事件
  3. 声明一个TouchTarget类型变量newTouchTarget, TouchTarget是一个单链表结构,在下面会介绍到。

简单介绍下:intercepted和canceled会直接影响第二部分代码是否执行if (!canceled && !intercepted),在没有取消和没有拦截的情况下才会走第二部分代码。newTouchTarget也和重要,在第二部分判断中if (newTouchTarget == null && childrenCount != 0)只有等于空和ViewGroup中有View才能开始循环


	final boolean intercepted;
	if (actionMasked == MotionEvent.ACTION_DOWN
                    || mFirstTouchTarget != null) {
	  ...
	  intercepted = onInterceptTouchEvent(ev)
	  ...
	}else{
	  intercepted = false
	}
	 final boolean canceled = resetCancelNextUpFlag(this)
                    || actionMasked == MotionEvent.ACTION_CANCEL;
                    
     TouchTarget newTouchTarget = null;
     

得到newTouchTarget

 final View[] children = mChildren; mChildren是ViewGroup中的子View集合,比如ViewGroup中有A,B ,C,三个View,A中还有子View,但是在mChildren只有A、B、C,也就是只包含这个视图的子视图。

第二部分源码,主要是获取事件分发的消耗树。 首先是判断拦截和取消,并且这个判断if (actionMasked == MotionEvent.ACTION_DOWN || 会让ACTION_MOVE和ACTION_UP类型的事件不能进入方法,也就是只有第一次的ACTION_DOWN能进入。

  • 首先看for (int i = childrenCount - 1; i >= 0; i--),用于遍历ViewGroup里面的View
  • 然后就是两个重要方法调用dispatchTransformedTouchEventaddTouchTarget

我们要知道在循环下,会找到一条事件的消耗树,这一个消耗树就是事件从activity传递到触摸View的路径。dispatchTransformedTouchEvent方法就是判断那一个View会进行消耗;如果它会消耗,那么在addTouchTarget方法里面有一个TouchTarget变量用于保存这个View,由于TouchTarget是一个链表,那么他就能将整个事件链收集起来。

	//2 没有拦截和取消的情况下得到mFirstTouchTarget
	if (!canceled && !intercepted) {
	//下面代码只针对ACTION_DOWN事件
		    if (actionMasked == MotionEvent.ACTION_DOWN
                        || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                        || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
                        
		 for (int i = childrenCount - 1; i >= 0; i--) {
		 //遍历ViewGroup中的View
			final View child = getAndVerifyPreorderedView(
                                    preorderedList, children, childIndex);
            //关键步骤                        
            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
              newTouchTarget = addTouchTarget(child, idBitsToAssign);
            }                         
		}
	 }
	}

...
dispatchTransformedTouchEvent

dispatchTransformedTouchEvent()会根据传入的child是否为空去调用相应的方法。

  1. 当child == null时会调用super.dispatchTouchEvent(event)也就是交给自身的dispatchTouchEvent(),如果自己不处理,那么往上抛给它的父View或Activity
  2. 当child != null时会调用该子view的dispatchTouchEvent(event)处理,当然该view可能是一个View也可能是一个ViewGroup,也就是说调用的可能是view的dispatchTouchEvent(),也可能是ViewGroup的dispatchTouchEvent(),

如果是ViewGroup那就在循环我们讲解的过程,我们直接看child == null的时候,看下面一小节

	
	    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);
            //当child == null时会将Touch事件传递给该ViewGroup自身的dispatchTouchEvent()处理
            if (child == null) {
                handled = super.dispatchTouchEvent(event);
            } else {
            //当child != null时会调用该子view(当然该view可能是一个View也可能是一个ViewGroup)的dispatchTouchEvent(event)处理.
                handled = child.dispatchTouchEvent(event);
            }
            event.setAction(oldAction);
            return handled;
        }

    ...
 
        final MotionEvent transformedEvent;
        if (newPointerIdBits == oldPointerIdBits) {
            if (child == null || child.hasIdentityMatrix()) {
                if (child == null) {
                    handled = super.dispatchTouchEvent(event);
                } else {
                    final float offsetX = mScrollX - child.mLeft;
                    final float offsetY = mScrollY - child.mTop;
                    event.offsetLocation(offsetX, offsetY);

                    handled = child.dispatchTouchEvent(event);

                    event.offsetLocation(-offsetX, -offsetY);
                }
                return handled;
            }
            transformedEvent = MotionEvent.obtain(event);
        } else {
            transformedEvent = event.split(newPointerIdBits);
        }

        // Perform any necessary transformations and dispatch.
        if (child == null) {
            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());
            }

            handled = child.dispatchTouchEvent(transformedEvent);
        }

        // Done.
        transformedEvent.recycle();
        return handled;
    }
View的dispatchTouchEvent()核心方法

如果是ViewGroup那就在循环我们讲解的过程,我们直接看child == null的时候,看View的dispatchTouchEvent()核心方法,后面会单独讲解View的dispatchTouchEvent()
在View的dispatchTouchEvent()中有一段代码

  1. li.mOnTouchListener.onTouch(this, event):当view设置了onTouch事件并且返回true,那么dispatchTransformedTouchEvent就返回true,并且这个子View的onTouchEvent都不能执行
  2. if (!result && onTouchEvent(event)):当onTouch返回false 或者没有设置这个事件,但是onTouchEvent返回true。那么dispatchTransformedTouchEvent照样返回true。
	if (onFilterTouchEventForSecurity(event)) {
            if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
                result = true;
            }
            //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;
            }
        }
addTouchTarget

这里可以看到,如果调用了addTouchTarget,那么会生成一个TouchTarget,并且将生产的target设置为mFirstTouchTarget。每个ViewGroup都持有一个mFirstTouchTarget变量,该变量指向一个TouchTarget链表(不带头结点)的首结点,该链表的结点用于存放当前ViewGroup的子结点,该子结点必须是“消耗树”上的结点。TouchTarget链表会在ACTION_DOWN被清空即“消耗树”消亡。总之,ViewGroup 的 TouchTarget链表存放该 ViewGroup 在“消耗树”上的所有子结点,mFirstTouchTarget指向它的首结点

	  private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
        final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
        target.next = mFirstTouchTarget;
        mFirstTouchTarget = target;
        return target;
    }

循环消耗树

第三部分源码:经过步骤2在ACTION_DOWN的时候已经收集到了消耗树,这里主要是判断消耗树,在ACTION_MOVE和ACTION_UP的时候遍历消耗树

  1. mFirstTouchTarget为空,那么就调用dispatchTransformedTouchEvent方法,这个方法传入的childView为空,就会将事件交给自己的dispatchTouchEvent()处理,自己不处理就想上抛
  2. mFirstTouchTarget不为null即找到了可以消费Touch事件的子View且后续Touch事件可以传递到该子View,那么久开始循环消耗树将事件开始传递,这里要知道,能进入到这里的循环只有ACTION_MOVE和ACTION_UP事件。
	//3 根据newTouchTarget的值调用dispatchTransformedTouchEvent()方法,判断中不同的地方在于传入的第三个参数一个是null一个是子View
 	if (mFirstTouchTarget == null) {
 	 handled = dispatchTransformedTouchEvent(ev, canceled, null,
                        TouchTarget.ALL_POINTER_IDS);
 	}else{
 	  TouchTarget target = mFirstTouchTarget;
      while (target != null) {
	  	if (dispatchTransformedTouchEvent(ev, cancelChild,
                                target.child, target.pointerIdBits)) {
         		handled = true;
                        }
	}
 }

dispatchTouchEvent

这里是ViewGroup的dispatchTouchEvent全部代码,代码中有注释讲解。

public abstract class ViewGroup extends View implements ViewParent, ViewManager {
...
	 @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
        }
        
        if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {
            ev.setTargetAccessibilityFocus(false);
        }
        //是否已经处理
        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) {
            //当开始新的触摸手势时,扔掉所有以前的状态,将mFirstTouchTarget设置为null , mFirstTouchTarget是触摸目标链接列表中的第一个触摸目标。
                cancelAndClearTouchTargets(ev);
            //重置Touch状态标识
                resetTouchState();
            }

            // 检查是否有拦截
            // 使用变量intercepted来标记ViewGroup是否拦截Touch事件的传递.
            final boolean intercepted;
            //事件为ACTION_DOWN或者mFirstTouchTarget不为null(即已经找到能够接收touch事件的目标组件)时if成立 
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || mFirstTouchTarget != null) {
            //判断disallowIntercept(禁止拦截)标志位 
            //可调用requestDisallowInterceptTouchEvent(boolean disallowIntercept)方法,禁止执行是否需要拦截的判断,所以判断标志位       
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                if (!disallowIntercept) {
               //当没有禁止时进行事件拦截
                    intercepted = onInterceptTouchEvent(ev);
                    ev.setAction(action); // restore action in case it was changed
                } else {
                //当禁止拦截判断时(即disallowIntercept为true)设置intercepted = false 
                    intercepted = false;
                }
            } else {
         
                //没有触摸目标并且此操作不是初始向下操作,因此设置 intercepted = true表示ViewGroup执行Touch事件拦截的操作。
                intercepted = true;
            }

            // 如果拦截,开始正常事件调度。如果有一个视图是处理手势,则进行正常事件调度
            if (intercepted || mFirstTouchTarget != null) {
                ev.setTargetAccessibilityFocus(false);
            }

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

            // 如果需要,更新指针向下的触摸目标列表。
            final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
            TouchTarget newTouchTarget = null;
            boolean alreadyDispatchedToNewTouchTarget = false;
            //如果没有取消,也没有拦截
            if (!canceled && !intercepted) {
            
                View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
                        ? findChildWithAccessibilityFocus() : null;

                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;

                    //清除此指针ID的早期触摸目标,以防它们不同步。
                    removePointersFromTouchTargets(idBitsToAssign);

                    final int childrenCount = mChildrenCount;
                    
                    if (newTouchTarget == null && childrenCount != 0) {
                        final float x = ev.getX(actionIndex);
                        final float y = ev.getY(actionIndex);
                       //  找到可以接收事件的子级。从前到后扫描子视图
                        final ArrayList<View> preorderedList = buildTouchDispatchChildList();
                        final boolean customOrder = preorderedList == null
                                && isChildrenDrawingOrderEnabled();
                        //此视图组的子视图
                        final View[] children = mChildren;
                        //childrenCount赋值在 addInArray()方法中 
                        for (int i = childrenCount - 1; i >= 0; i--) {
                            final int childIndex = getAndVerifyPreorderedIndex(
                                    childrenCount, i, customOrder);
                            final View child = getAndVerifyPreorderedView(
                                    preorderedList, children, childIndex);

          
                            if (childWithAccessibilityFocus != null) {
                                if (childWithAccessibilityFocus != child) {
                                    continue;
                                }
                                childWithAccessibilityFocus = null;
                                i = childrenCount - 1;
                            }

                            if (!canViewReceivePointerEvents(child)
                                    || !isTransformedTouchPointInView(x, y, child, null)) {
                                ev.setTargetAccessibilityFocus(false);
                                continue;
                            }

                            newTouchTarget = getTouchTarget(child);
                            if (newTouchTarget != null) {
                                // 找到接收Touch事件的子View
                                newTouchTarget.pointerIdBits |= idBitsToAssign;
                                break;
                            }

                            resetCancelNextUpFlag(child);
                            //dispatchTransformedTouchEvent()会调用子元素的dispatchTouchEvent()。
                            /*
                            *如果dispatchTransformedTouchEvent()返回false即子View 
                               的onTouchEvent返回false(即Touch事件未被消费)那么就不满足该if条件,也就无法执行addTouchTarget() 
                               从而导致mFirstTouchTarget为null.那么该子View就无法继续处理ACTION_MOVE事件 
                               和ACTION_UP事件
                            */
                            
                            /*
                            * 对于此处ACTION_DOWN的处理具体体现在dispatchTransformedTouchEvent()
                            *  该方法返回boolean,如下:
                            *  true---->事件被消费----->mFirstTouchTarget!=null
                            *  false--->事件未被消费---->mFirstTouchTarget==null
                            *  因为在dispatchTransformedTouchEvent()会调用递归调用dispatchTouchEvent()和onTouchEvent()
                            * 所以dispatchTransformedTouchEvent()的返回值实际上是由onTouchEvent()决定的.
                            * 简单地说onTouchEvent()是否消费了Touch事件(true or false)的返回值决定了dispatchTransformedTouchEvent() 的返回值,从而决定了mFirstTouchTarget是否为null,从而进一步决定了ViewGroup是否
                *   处理Touch事件
                            */
                            
                               /**
        	 				* 如果dispatchTransformedTouchEvent()返回true即子View 
        	 				*   的onTouchEvent返回true(即Touch事件被消费)那么就满足该if条件. 
         					*  从而mFirstTouchTarget不为null
         					*/

                            //该方法返回true则表示子View消费掉该事件,同时进入该if判断
                            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                                // 子视图想要在触摸范围内接受触摸
                                mLastTouchDownTime = ev.getDownTime();
                                if (preorderedList != null) {
                             
                                    //子视图的下标在 presorted集合中,找到原来的下标
                                    for (int j = 0; j < childrenCount; j++) {
                                        if (children[childIndex] == mChildren[j]) {
                                            mLastTouchDownIndex = j;
                                            break;
                                        }
                                    }
                                } else {
                                    mLastTouchDownIndex = childIndex;
                                }
                                mLastTouchDownX = ev.getX();
                                mLastTouchDownY = ev.getY();
                          
                                //将ViewGroup中触摸的View添加到列表开头
                                newTouchTarget = addTouchTarget(child, idBitsToAssign);
                                //已经将Touch派发给新的TouchTarget
                                alreadyDispatchedToNewTouchTarget = true;
                                break;
                            }

                       
                            ev.setTargetAccessibilityFocus(false);
                        }
                        if (preorderedList != null) preorderedList.clear();
                    }
					
                    if (newTouchTarget == null && mFirstTouchTarget != null) {
                        //找不到要接收事件的子级。将指针分配给最近添加的目标
                        newTouchTarget = mFirstTouchTarget;
                        while (newTouchTarget.next != null) {
                            newTouchTarget = newTouchTarget.next;
                        }
                        //newTouchTarget指向了最初的TouchTarget
                        newTouchTarget.pointerIdBits |= idBitsToAssign;
                    }
                }
            }
   			/*
   			* 将touch事件派发给目标View
   			* 经过上面对于ACTION_DOWN的处理后mFirstTouchTarget有两种情况:
   			* 1 mFirstTouchTarget为null
   			* 2 mFirstTouchTarget不为null 
   			* 并且上面那些复杂的流程只是针对ACTION_DOWN
   			* 而是从此处开始执行,比如ACTION_MOVE和ACTION_UP 
   			*/			
            
            if (mFirstTouchTarget == null) {
            /*
            * 情况1:mFirstTouchTarget为null
            * mFirstTouchTarget == null 即没有找到能够消费touch事件的子组件或Touch事件被拦截了
            * 则调用ViewGroup的dispatchTransformedTouchEvent()方法处理Touch事件则和普通View一样
            * 即子View没有消费Touch事件,那么子View的上层ViewGroup才会调用其onTouchEvent()处理Touch事件.
            * 也就是说此时ViewGroup像一个普通的View那样调用dispatchTouchEvent(),且在dispatchTouchEvent()中会去调用onTouchEvent()方法
            * 这就是为什么子view对于Touch事件处理返回true那么其上层的ViewGroup就无法处理Touch事件了
            */
               
             
                handled = dispatchTransformedTouchEvent(ev, canceled, null,
                        TouchTarget.ALL_POINTER_IDS);
            } else {
            /**
            * 情况2: FirstTouchTarget不为null即找到了可以消费Touch事件的子View且后续Touch事件可以传递到该子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;
                         //对于非ACTION_DOWN事件继续传递给目标子组件进行处理,依然是递归调用dispatchTransformedTouchEvent()        
                        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;
                }
            }
			 /** 
             * 处理ACTION_UP和ACTION_CANCEL 
             * 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;
    }
...

   
...
...
}

onInterceptTouchEvent


  	//判断是否事件拦截
	  public boolean onInterceptTouchEvent(MotionEvent ev) {
        if (ev.isFromSource(InputDevice.SOURCE_MOUSE)
                && ev.getAction() == MotionEvent.ACTION_DOWN
                && ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY)
                && isOnScrollbarThumb(ev.getX(), ev.getY())) {
            return true;
        }
        return false;
    }

View

上面我们知道了ViewGroup的dispatchTouchEvent方法调用的流程,我们知道View的dispatchTouchEvent方法被调用时在ViewGroup的 dispatchTransformedTouchEvent方法中,那么View的dispatchTouchEvent主要做了什么操作呢

dispatchTouchEvent

当我们对一个View设置setOnTouchListener和setOnClickListener的时候,我们知道如果setOnTouchListener返回true,则不会调用setOnClickListener方法,在源码里面我们知道为什么,以为setOnTouchListener返回true,那么if (!result && onTouchEvent(event))就不成立,在View的onTouchEvent方法里面才调用了OnClickListener事件

对于view的dispatchTouchEvent方法,核心就是上面介绍ViewGroup时候介绍的。

public class View implements Drawable.Callback, KeyEvent.Callback,
        AccessibilityEventSource {
...
	public boolean dispatchTouchEvent(MotionEvent event) {
        // 如果事件应首先由可访问性焦点处理。
        if (event.isTargetAccessibilityFocus()) {
            // We don't have focus or no virtual descendant has it, do not handle the event.
            if (!isAccessibilityFocusedViewOrHost()) {
                return false;
            }
            // 我们有焦点并得到了事件,然后使用正常的事件发送.
            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)) {
            if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
                result = true;
            }
            //若设置了OnTouchListener,则先调用onTouch()。
            ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnTouchListener != null
                    && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) {
                result = true;
            }
            //若onTouch()没有消耗事件则调用onTouchEvent()
            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;
    }
    
...
}

onTouchEvent

对于onTouchEvent方法看 performClickInternal(),其中调用performClick(),最后能看到熟悉的 li.mOnClickListener.onClick(this);

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

        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;
        }
        if (mTouchDelegate != null) {
            if (mTouchDelegate.onTouchEvent(event)) {
                return true;
            }
        }

        if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
            switch (action) {
                case MotionEvent.ACTION_UP:
                    mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                    if ((viewFlags & TOOLTIP) == TOOLTIP) {
                        handleTooltipUp();
                    }
                    if (!clickable) {
                        removeTapCallback();
                        removeLongPressCallback();
                        mInContextButtonPress = false;
                        mHasPerformedLongPress = false;
                        mIgnoreNextUpEvent = false;
                        break;
                    }
                    boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
                    if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
                        // take focus if we don't have it already and we should in
                        // touch mode.
                        boolean focusTaken = false;
                        if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
                            focusTaken = requestFocus();
                        }

                        if (prepressed) {
                            // The button is being released before we actually
                            // showed it as pressed.  Make it show the pressed
                            // state now (before scheduling the click) to ensure
                            // the user sees it.
                            setPressed(true, x, y);
                        }

                        if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
                            // This is a tap, so remove the longpress check
                            removeLongPressCallback();

                            // Only perform take click actions if we were in the pressed state
                            if (!focusTaken) {
                                // Use a Runnable and post this rather than calling
                                // performClick directly. This lets other visual state
                                // of the view update before click actions start.
                                if (mPerformClick == null) {
                                    mPerformClick = new PerformClick();
                                }
                                if (!post(mPerformClick)) {
                                    performClickInternal();
                                }
                            }
                        }

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

                        if (prepressed) {
                            postDelayed(mUnsetPressedState,
                                    ViewConfiguration.getPressedStateDuration());
                        } else if (!post(mUnsetPressedState)) {
                            // If the post failed, unpress right now
                            mUnsetPressedState.run();
                        }

                        removeTapCallback();
                    }
                    mIgnoreNextUpEvent = false;
                    break;

                case MotionEvent.ACTION_DOWN:
                    if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN) {
                        mPrivateFlags3 |= PFLAG3_FINGER_DOWN;
                    }
                    mHasPerformedLongPress = false;

                    if (!clickable) {
                        checkForLongClick(0, x, y);
                        break;
                    }

                    if (performButtonActionOnTouchDown(event)) {
                        break;
                    }

                    // Walk up the hierarchy to determine if we're inside a scrolling container.
                    boolean isInScrollingContainer = isInScrollingContainer();

                    // For views inside a scrolling container, delay the pressed feedback for
                    // a short period in case this is a scroll.
                    if (isInScrollingContainer) {
                        mPrivateFlags |= PFLAG_PREPRESSED;
                        if (mPendingCheckForTap == null) {
                            mPendingCheckForTap = new CheckForTap();
                        }
                        mPendingCheckForTap.x = event.getX();
                        mPendingCheckForTap.y = event.getY();
                        postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
                    } else {
                        // Not inside a scrolling container, so show the feedback right away
                        setPressed(true, x, y);
                        checkForLongClick(0, x, y);
                    }
                    break;

                case MotionEvent.ACTION_CANCEL:
                    if (clickable) {
                        setPressed(false);
                    }
                    removeTapCallback();
                    removeLongPressCallback();
                    mInContextButtonPress = false;
                    mHasPerformedLongPress = false;
                    mIgnoreNextUpEvent = false;
                    mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                    break;

                case MotionEvent.ACTION_MOVE:
                    if (clickable) {
                        drawableHotspotChanged(x, y);
                    }

                    // Be lenient about moving outside of buttons
                    if (!pointInView(x, y, mTouchSlop)) {
                        // Outside button
                        // Remove any future long press/tap checks
                        removeTapCallback();
                        removeLongPressCallback();
                        if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
                            setPressed(false);
                        }
                        mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                    }
                    break;
            }

            return true;
        }

        return false;
    }

小结:整个事件分发的关键步骤还是在ViewGroup的dispatchTouchEvent方法里面,这个方法里面dispatchTransformedTouchEvent方法涉及到View的dispatchTouchEvent的是需要理解的核心。
但整个事件分发的逻辑还是离不开上面的U型图,然后每个流程可以看下面这张图。图片来源为下面连接中博客。

在这里插入图片描述
最后以一个问题来结束本篇文章:如果有一个ViewGroup,里面有一个ChildView A,当只设置ChildView A的OnTouchListener事件并返回false,点击ChildView A同时并移动手指,那么ChildView A能否接收到ACTION_MOVE事件?为什么?如果返回true呢?

当返回false,A是Activity,F是父View,C2222代表ChildView

	07-12 19:18:25.661 23424-23424/com.hongliang.demo I/sunzn: ===A=== dispatchTouchEvent --> ACTION_DOWN
    ===F=== dispatchTouchEvent --> ACTION_DOWN
    ===F=== onInterceptTouchEvent --> ACTION_DOWN
    ===C2222=== dispatchTouchEvent --> ACTION_DOWN
    onTouch------------
    ===C2222=== onTouchEvent --> ACTION_DOWN
    ===F===  onTouchEvent --> ACTION_DOWN
    ===A===  onTouchEvent --> ACTION_DOWN
07-12 19:18:25.680 23424-23424/com.hongliang.demo I/sunzn: ===A=== dispatchTouchEvent --> ACTION_MOVE
    ===A===  onTouchEvent --> ACTION_MOVE
07-12 19:18:25.688 23424-23424/com.hongliang.demo I/sunzn: ===A=== dispatchTouchEvent --> ACTION_MOVE
    ===A===  onTouchEvent --> ACTION_MOVE
    ===A=== dispatchTouchEvent --> ACTION_UP
    ===A===  onTouchEvent --> ACTION_UP

当返回true

	07-12 19:16:39.863 23003-23003/com.hongliang.demo I/sunzn: ===A=== dispatchTouchEvent --> ACTION_DOWN
    ===F=== dispatchTouchEvent --> ACTION_DOWN
    ===F=== onInterceptTouchEvent --> ACTION_DOWN
    ===C2222=== dispatchTouchEvent --> ACTION_DOWN
    onTouch------------
07-12 19:16:39.875 23003-23003/com.hongliang.demo I/sunzn: ===A=== dispatchTouchEvent --> ACTION_MOVE
    ===F=== dispatchTouchEvent --> ACTION_MOVE
07-12 19:16:39.876 23003-23003/com.hongliang.demo I/sunzn: ===F=== onInterceptTouchEvent --> ACTION_MOVE
    ===C2222=== dispatchTouchEvent --> ACTION_MOVE
    onTouch------------
07-12 19:16:39.895 23003-23003/com.hongliang.demo I/sunzn: ===A=== dispatchTouchEvent --> ACTION_MOVE
07-12 19:16:39.896 23003-23003/com.hongliang.demo I/sunzn: ===F=== dispatchTouchEvent --> ACTION_MOVE
    ===F=== onInterceptTouchEvent --> ACTION_MOVE
    ===C2222=== dispatchTouchEvent --> ACTION_MOVE
    onTouch------------
07-12 19:16:39.909 23003-23003/com.hongliang.demo I/sunzn: ===A=== dispatchTouchEvent --> ACTION_MOVE
    ===F=== dispatchTouchEvent --> ACTION_MOVE
    ===F=== onInterceptTouchEvent --> ACTION_MOVE
    ===C2222=== dispatchTouchEvent --> ACTION_MOVE
    onTouch------------
07-12 19:16:39.927 23003-23003/com.hongliang.demo I/sunzn: ===A=== dispatchTouchEvent --> ACTION_MOVE
    ===F=== dispatchTouchEvent --> ACTION_MOVE
    ===F=== onInterceptTouchEvent --> ACTION_MOVE
    ===C2222=== dispatchTouchEvent --> ACTION_MOVE
07-12 19:16:39.928 23003-23003/com.hongliang.demo I/sunzn: onTouch------------
    ===A=== dispatchTouchEvent --> ACTION_UP
    ===F=== dispatchTouchEvent --> ACTION_UP
    ===F=== onInterceptTouchEvent --> ACTION_UP
    ===C2222=== dispatchTouchEvent --> ACTION_UP
    onTouch------------

为什么是C2222dispatchTouchEvent打印ACTION_MOVE,而不是onTouchEvent?

Android事件分发机制本质是树的深度遍历
dispatchTouchEvent源码解析
图解 Android 事件分发机制
Android触摸事件全过程分析:由产生到Activity.dispatchTouchEvent()

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值