观Android事件分发源码有感

前言

已经在工作android码农朋友一定都知道一件事,那么就是到一个公司无论是作为吉祥物还是作为公司的android架构师,面试的时候逃不掉的一个问题就是“Android 的事件分发机制”。并且在实际的抄袭网上代码的过程中,我们经常也会遇到各种事件冲突的问题。一般我们都是网上搜索一番,cv一些不知所云的代码进入工程了事。

但是恰好今日有空,我们不妨来细究一下Android的源码。看看这些所谓的事件到底是怎么回事。

MotionEvent.ACTION_DOWN

事件分发

通过一个日常的经验,我们可以发现,无论是点击事件,还是移动事件。所有事件的开端都是从用户按下开始的,所以我们现在来研究一下ACTION_DOWN事件。

Activity

在看源码之前,我想请大家接受一个不太正确的观念,那么就是用户在点击了手机屏幕以后,事件分发是从Activity的dispatchTouchEvent方法开始的。因为只有找到一个源头以后,我们才方便开始源码之旅。
好了接下来我们就看一下dispatchTouchEvent方法的源码:

	//点击事件的分发
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
        	//这里默认是一个空方法,如果码农有需要,可以自己去复写这个方法,
        	实现对于用户的按下屏幕事件的监听
            onUserInteraction();	
        }
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        return onTouchEvent(ev);
    }
    
    //点击事件的消费
    public boolean onTouchEvent(MotionEvent event) {
        if (mWindow.shouldCloseOnTouch(this, event)) {
            finish();
            return true;
        }

        return false;
    }

上面的代码,就是activity中关于事件分发和消费的所以代码,是不是非常简单?通过观察上面的代码,我们很容易就是找到函数中的关键代码就是

getWindow().superDispatchTouchEvent(ev)

根据我们的猜测,这句话应该就是实现了activity事件向布局的传递。那么这个getWindow又是什么玩意。我们接着往下看。

    public Window getWindow() {
        return mWindow;
    }

原来是获取Activity所持有的一个Window对象,那么这个window又是何方神圣?经过我的一分寻找,发现了它被赋值的地方。

 final void attach(
				//省略具体参数
									) {
		//省略无关代码
        mWindow = new PhoneWindow(this, window, activityConfigCallback);
        mWindow.setWindowControllerCallback(this);
        mWindow.setCallback(this);
        mWindow.setOnWindowDismissedCallback(this);
        mWindow.getLayoutInflater().setPrivateFactory(this);
       //省略无关代码
    }

原来这里的window是一个PhoneWindow的实现。如果大家看过activity和window以及DecorView的关系的话,一定能很快明白过来。这里我就不细讲了,有兴趣的小伙伴可以看下这篇博文
Activity 与 Window、PhoneWindow、DecorView 之间的关系详解

其实上面的整篇文章讲的就是一张图片。来上图!
在这里插入图片描述
从上图可以看出,我们的整个Activity就是持有了一个PhoneWindow,然后PhoneWindow持有了一个DecorView,最后DecorView持有了我们在setcontentView中塞进去的布局。
好了有了以上的知识,我们就可以转移阵地了,我们去看看PhoneWindow中收到消息究竟做了啥

PhoneWindow和DecorView

接下来,我们看看在Activity调用了getWindow().superDispatchTouchEvent(ev)之后的故事。

   private DecorView mDecor;

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

好嘛!这个PhoneWindow也太会偷懒了,收到了事件以后,直接就将足球踢给了DecorView。那我们接着转移阵地。

public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {
	//省略无关代码...
    public boolean superDispatchTouchEvent(MotionEvent event) {
        return super.dispatchTouchEvent(event);
    }
   //省略无关代码...
 }

这个DecorView不愧是PhoneWindow的好基友,对于事件的处理都是一脉相承,直接调用了父类的事件处理分发方法。而DecorView的父类就是大名鼎鼎的FrameLayout。所以,我们进入下一个章节。

ViewGroup

我们一路跟踪用户的点击事件,最终到了DecorView这一步,把锅甩给了我们熟悉的ViewGroup。ok!上代码!

 @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
    	//省略无关代码。。。。
        boolean handled = false;
        if (onFilterTouchEventForSecurity(ev)) {
            final int action = ev.getAction(); //获取事件类型
            final int actionMasked = action & MotionEvent.ACTION_MASK; //多点触控相关

          	//省略无关代码。。。。

            // Check for interception.
            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;
            }

		}
         
 	 	//省略无关代码。。。。
        return handled;
    }

viewGroup中的dispatchTouchEvent方法很长,我简化了一下。通过观察上面的代码片段,发现event事件被onInterceptTouchEvent方法去处理。但是在调用这个方法前,有两个判断会影响onInterceptTouchEvent去处理event事件。首先是

if (actionMasked == MotionEvent.ACTION_DOWN|| mFirstTouchTarget != null) {

这句话,第一个条件很容易理解,就是必须是DOWN事件。而第二个条件mFirstTouchTarget,我们这里先留个悬念,后面会遇到的他。
第二个语句就是

 final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                if (!disallowIntercept) {

这里的话mGroupFlags & FLAG_DISALLOW_INTERCEPT 是什么玩意,我们在viewgourp中全局搜索一下,发现了以下方法可以设置mGroupFlags & FLAG_DISALLOW_INTERCEPT 。

    @Override
    public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {

        if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) {
            // We're already in this state, assume our ancestors are too
            return;
        }

        if (disallowIntercept) {
            mGroupFlags |= FLAG_DISALLOW_INTERCEPT;
        } else {
            mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
        }

        // Pass it up to our parent
        if (mParent != null) {
            mParent.requestDisallowInterceptTouchEvent(disallowIntercept);
        }
    }

这其实就是viewgourp开发给其他地方的一个拦截标志位的设置,调用方可以选择是否拦截event事件。
不过除了requestDisallowInterceptTouchEvent可以操作该标志位,我们还发现了一个私有方法

    /**
     * Resets all touch state in preparation for a new cycle.
     */
    private void resetTouchState() {
        clearTouchTargets();
        resetCancelNextUpFlag(this);
        mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
        mNestedScrollAxes = SCROLL_AXIS_NONE;
    }

那么这方法在什么时候调用呢?一共有两处,其中一处很凑巧,就是在前面的dispatchTouchEvent方法中

			// 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);
            }

看了上面代码, 我们会发现在MotionEvent.ACTION_UP和MotionEvent.ACTION_HOVER_MOVE的时候,会被重置,而MotionEvent.ACTION_HOVER_MOVE是鼠标相关的方法我们不用关注。所以也就是说,用户手机离开屏幕的时候mGroupFlags & FLAG_DISALLOW_INTERCEPT会被重置。

而且如果你现在就在对照源码看的话,你还会发现,其实在之前Viewgroup中的dispatchTouchEvent方法中,检测到如果event事件为Down的话,也会调用resetTouchState方法。所以mGroupFlags标志位,只要在用户已经触发点击事情之后,Up事件之前改变才有效果。

啊哦,好像扯的有点远了,我们接着回归主流程。继续观察ViewGroup.dispatchTouchEvent中的语句 intercepted = onInterceptTouchEvent(ev);

可以见到,事件被分发到了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;
    }

可以见到这个方法,对于我们来说并没有什么实际的内容。重点则是应该放在这个方法的返回值方面。在ViewGroup.dispatchTouchEvent方法中onInterceptTouchEvent返回值被保存到了变量intercepted方法中。而我们在ViewGroup.dispatchTouchEvent中发现了下面的源码

if (!canceled && !intercepted) {
				//ACTION_DOWN 为屏幕第一次用手指按下,ACTION_POINTER_DOWN为多指触控时,
				//在已经有一个手指没有释放屏幕的时候,再次用另外一个手指按下
                if (actionMasked == MotionEvent.ACTION_DOWN
                        || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                        || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
                    final int actionIndex = ev.getActionIndex(); // always 0 for down
                    final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
                            : TouchTarget.ALL_POINTER_IDS;

                    // Clean up earlier touch targets for this pointer id in case they
                    // have become out of sync.
                    removePointersFromTouchTargets(idBitsToAssign);

                    final int childrenCount = mChildrenCount;  //获取子控件的数量
                    if (newTouchTarget == null && childrenCount != 0) {
                       			//省略若干代码
                        for (int i = childrenCount - 1; i >= 0; i--) {
              					//省略若干代码
              					//获取子元素的target。如果已经在链表中存储过,则直接返回,否则返回null。主要多指的时候多同一个view多次点击
              					 newTouchTarget = getTouchTarget(child); 
                            if (newTouchTarget != null) {
                                // Child is already receiving touch within its bounds.
                                // Give it the new pointer in addition to the ones it is handling.
                                //如已经子元素已经在链表中查找到,则不在遍历viewgroup的子元素,直接跳出,并更改pointerIdBits。
                                //此处pointerIdBits,可以用来更改分发到事件的类型。例如如果两个view重叠到一起,第一次down事件被下面的view消费,
                                //第二次down事件的时候被上面的view消费。这次的down事件分发到下面的view的时候就会变成move事件。
                                newTouchTarget.pointerIdBits |= idBitsToAssign;
                                break;
                            }
                            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {  //将event时间向下级的view分发
                                // Child wants to receive touch within its bounds.
                                mLastTouchDownTime = ev.getDownTime();
                                if (preorderedList != null) {
                                    // childIndex points into presorted list, find original index
                                    for (int j = 0; j < childrenCount; j++) {
                                        if (children[childIndex] == mChildren[j]) {
                                            mLastTouchDownIndex = j;
                                            break;
                                        }
                                    }
                                } else {
                                    mLastTouchDownIndex = childIndex;
                                }
                               //省略若干代码
                            }

                            // The accessibility focus didn't handle the event, so clear
                            // the flag and do a normal dispatch to all children.
                            ev.setTargetAccessibilityFocus(false);
                        }
                        if (preorderedList != null) preorderedList.clear();
                    }
						//省略若干代码
                }
            }

在这个方法里面我们终于发现了事件向下方法的相关代码就是dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)这句话。而这条语句之前的前提是intercepted为false,也就是onInterceptTouchEvent方法返回值为flase,也就是不会被拦截。所以我们可以通过复写onInterceptTouchEvent方法来实现对于event事件的拦截。

那么接下来我们趁热打铁来看一下dispatchTransformedTouchEvent的源码

    private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits) {
        final boolean handled;

		   // If the number of pointers is the same and we don't need to perform any fancy
        // irreversible transformations, then we can reuse the motion event for this
        // dispatch as long as we are careful to revert any changes we make.
        // Otherwise we need to make a copy.
        //这里要特别注意一件事,就是android的事件分发并不是简单意义上的直接把事件向下方法,而是会根据子view的不同状态方法不同的事件。
        //这里我举一个例子,假如我们有两个view重叠在一起,我们把上面的view叫做v1,下面的称作v2。
        //我们在第一次点击这个view的时候,v1不对事件做处理,v2消费事件。这样的话,v1收到的是DOWN事件,v2收到的也是DOWN
        //这时候我们不放开第一个手指,再用另一个手指点击v1的时候,v1同样不做处理,v2消费事件。在此次事件中,你就会发现一个很有趣的现象
        //就是v1收到的事件是DOWN,因为v1从来没有消费过Down事件。而v2收到事件是ACTION_POINTER_DOWN。
        //因为v2已经有一个DOWN事件了。而这里的改变事件的“罪魁祸首”就是下面的代码了!
        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);
        }

		//省略部分代码
		 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);
        }
           transformedEvent.recycle();
        return handled;
    }

这个方法的逻辑相信大家应该都能看懂,很简单的。就是如果child不为空的话。那么就调用child的dispatchTouchEvent的方法,如果child为空,那么就调用父类的dispatchTouchEvent方法,而我们都知道ViewGroup的父类是view,所以也就是调用view的dispatchTouchEvent方法。所以接下来我们就要细究一下view的dispatchTouchEvent方法。

View

既然viewgroup中调用了view的dispatchTouchEvent方法,那么老规矩dispatchTouchEvent源码奉上。

 public boolean dispatchTouchEvent(MotionEvent event) {
    	...
        if (onFilterTouchEventForSecurity(event)) {
			....
            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设置了OnTouchListener,并且mOnTouchListener返回为true的话,那么就不在执行自身的onTouchEvent方法。否则的话将事件传递到onTouchEvent。

接下来我们就查看一下onTouchEvent的相关源码

算了不看了,太长了,基本上就是对event事件的处理,并没有再次分发。

事件冒泡

原谅我在这里创建一个名词,事件冒泡。但是你想想,当事件走到了我们的view的onTouchEvent以后,完成onTouchEvent以后,再完成view.dispatchTouchEvent事件,以此类推,像不像冒泡。

view

这里我们假设view.dispatchTouchEvent返回值是true

  public boolean dispatchTouchEvent(MotionEvent event) {
		...
        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;
            }
        }

        if (!result && mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
        }
		...
        return result;
    }

观察上面代码,当ontouchevent返回为true的时候,view.dispatchOntouchEvent并没有做其他事情,只是将result置为true并继续向上传递。所以接下来我们去看viewGroup的源码

viewGroup

  private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits) {
        final boolean handled;
			...
               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;
                ...
    }

额,好像也只是把结果上传,那我们继续冒泡

public boolean dispatchTouchEvent(MotionEvent ev) {
	...
		 if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                                // Child wants to receive touch within its bounds.
   								...
                                newTouchTarget = addTouchTarget(child, idBitsToAssign);
                                alreadyDispatchedToNewTouchTarget = true;
                                break;
                            }
    ...
   
      return handled;
}

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

在这里的时候,如果子元素的dispatchTransformedTouchEvent返回为true,则会给viewgroup的mFirstTouchTarget打上target标签。

但是这是dispatchTransformedTouchEvent返回为true的情况,如果在down事件的时候,返回是false呢?
那么mFirstTouchTarget肯定就为null了,这时候dispatchTransformedTouchEvent有以下代码

 public boolean dispatchTouchEvent(MotionEvent ev) {
 // Dispatch to touch targets.
            if (mFirstTouchTarget == null) {
                // No touch targets so treat this as an ordinary view.
                handled = dispatchTransformedTouchEvent(ev, canceled, null,
                        TouchTarget.ALL_POINTER_IDS);
            } else {
                // Dispatch to touch targets, excluding the new touch target if we already
                // dispatched to it.  Cancel touch targets if necessary.
                TouchTarget predecessor = null;
                TouchTarget target = mFirstTouchTarget;
                while (target != null) {
                    final TouchTarget next = target.next;
                     //这里如果前面已经分发过事件了则alreadyDispatchedToNewTouchTarget为true。
                     //而 target == newTouchTarget则是继续链表中其他事件的分发
                    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;
                        }
                    }
                    predecessor = target;
                    target = next;
                }
            }
		}
    private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits) {
        final boolean 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;
    }

在这里我们又看到了我们熟悉的方法dispatchTransformedTouchEvent。只不过和之前不同的是,这次的子元素为null,所以直接调用了viewgroup自身的dispatchTouchEvent的方法。
好了那么总结以下,我们可以写出以下伪代码

if(dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)){
	return true;
}else{
	handled = super.dispatchTouchEvent(transformedEvent);
	return handled;
}

好了,这里搞定,我们技术往上看。

DecorView和PhoneWindow

DecorView

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

PhoneWindow

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

啥也木有,继续往上

Activity

    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            onUserInteraction();
        }
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        return onTouchEvent(ev);
    }

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

        return false;
    }

好嘛,逻辑也很简单了,如果之前一直都木有人处理,那么走到Activity这里的时候,就会走到activity的onTouchEvent方法。

MotionEvent.ACTION_UP

其中我忽略了move事件的处理,因为move事件的处理和up差不多。而且我们在平常的开发中对于move事件处理的不多,所以这里就暂时忽略了move事件的处理。

事件分发

这里由于activity和phoneWindow 以及DecorView对于事件并没有区分是什么是什么事件,所以处理方式和前面并没有什么区别,这里暂时就不看了。

ViewGroup

 @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
    		...
            boolean handled = false;
        if (onFilterTouchEventForSecurity(ev)) {
            final int action = ev.getAction();
            final int actionMasked = action & MotionEvent.ACTION_MASK;
            
			...
	
            // Check for interception.
            final boolean intercepted;
        	...
            // Dispatch to touch targets.
            if (mFirstTouchTarget == null) {
                // No touch targets so treat this as an ordinary view.
                handled = dispatchTransformedTouchEvent(ev, canceled, null,
                        TouchTarget.ALL_POINTER_IDS);
            } else {
                // Dispatch to touch targets, excluding the new touch target if we already
                // dispatched to it.  Cancel touch targets if necessary.
                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;
                        }
                    }
                    predecessor = target;
                    target = next;
                }
            }
			...
        }
		...
        return handled;
    }

这里可以看到UP事件走的逻辑和DOWN几乎完全不同。而且如果viewgroup的之前没有子元素的dispatchTouchEvent返回true的话,那么intercepted将会将为true。
并且由于mFirstTouchTarget==null,将会直接调用

 handled = dispatchTransformedTouchEvent(ev, canceled, null,
                        TouchTarget.ALL_POINTER_IDS);

语句,然后去调用自身的dispatchTouchEvent方法。也就是说如果viewgroup的子元素没有在down事件,dispatchTouchEvent方法的时候去返回true那么up事件将不再向下传递给子元素,而是直接由viewgroup本身处理。

那么我们假设之前viewgroup的子元素,有处理过down事件,那么和down事件一样,将调用

if (dispatchTransformedTouchEvent(ev, cancelChild,
                                target.child, target.pointerIdBits))

这条语句,继续将事件向子元素传递。只不过这里有一点和down不一样。down事件是通过遍历子元素,然后判断能否被点击,再去把事件传递。而在up事件的时候,没有进行遍历,而是直接调用之前保存的子元素,向他传递。

这里我们需要注意一点,那么就是viewgroup保存执行了down事件的子元素的时候,用的是一个链表。这就意味着,up事件是可以同时向多个子元素传递,只要之前消费down事件,那么都会收到viewgroup分发的事件。只不过viewgroup会根据每个子元素不同的状态方法不同的事件。这里需要大家多多注意,我也在这里困了一天之久!

View

按照惯例,接下来我们来观察view里面的dispatchTouchEvent方法。

好了你不用观察了,我帮你观察过了,和Down相比,没有任何区别。所以我们还是去看看onTouchEvent方法吧。

 public boolean onTouchEvent(MotionEvent event) {
		...
        if (((viewFlags & CLICKABLE) == CLICKABLE ||
                (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
                (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {
            switch (action) {
                case MotionEvent.ACTION_UP:
                    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)) {
                                    performClick();
                                }
                            }
                        }
					...
                    break;
                    ...
            }

            return true;
        }

        return false;
    }


    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);
        return result;
    }

哦了,这里代码也没啥。相信大家仔细看下就都能看懂。其中performClick就是用来去调用listener的监听。

总结

看了android关于事件分发的源码之后,我除了豁然开朗的感觉。还有对于google工程师的由衷的敬佩,自己看他们的代码都看了很久很久,才理清楚一点点的头绪,真是不能想象他们是如何写出如此优美的代码。自己真的要再修行,再修行!
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值