Android事件分发机制详解

事件分发(不阐述事件的产生,从java层开始分析)

总所周知,视图在安卓中是树的结构,当我们触摸屏幕,下层的电信号层层向上传递,传递到java层需要判断此事件交给哪个树节点,判断的过程就是分发的过程。

进入源码分析

1.事件分发的流程

请添加图片描述

//1.Activity#dispatchTouchEvent()
public boolean dispatchTouchEvent(MotionEvent ev) {
    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
        //空方法和屏保有关
        onUserInteraction();
    }
    //下一步Window唯一实现类就是PhoneWindow
    if (getWindow().superDispatchTouchEvent(ev)) {
        return true;
    }
    return onTouchEvent(ev);
}

//2.PhoneWindow.superDispatchTouchEvent()
public boolean superDispatchTouchEvent(MotionEvent event) {
    //下一步 mDecor就是DecorView
    return mDecor.superDispatchTouchEvent(event);
}

//3.DecorView.superDispatchTouchEvent()
public boolean superDispatchTouchEvent(MotionEvent event) {
    //DecorView继承自FrameLayout,FrameLayout继承自ViewGroup,FrameLayout没有重写dispatchTouchEvent(),所以会执行ViewGroup中的dispatchTouchEvent()
    return super.dispatchTouchEvent(event);
}

//4.ViewGroup的dispatchTouchEvent()
//5.ViewGroup可能会调用它子view的dispatchTouchEvent()
//6.View会调用自身的onTouchEvent()决定怎么执行

//4,5,6在后面详细讲解

2.事件在view中的流程

分析View中的分发,ViewGroup最终会调用ViewdispatchTouchEvent(),因此先看View中是怎么分发的,只关注重要代码

这里笔者提出一个问题

View为什么不直接处理而是需要再次分发,不是ViewGroup才需要分发吗?

在分析之前笔者浅谈一下对此问题的理解:View的分发是判断事件交给哪个方法执行,是执行监听器还是执行默认方法。

查看源码对上述理解进行验证

view#dispatchTouchEvent

public boolean dispatchTouchEvent(MotionEvent event) {
    
	...
        
    ListenerInfo li = mListenerInfo;
    //首先判断如果监听li对象!=null 且我们通过setOnTouchListener设置了监听,即是否有实现OnTouchListener,如果有实现就判断当前的view状态是不是ENABLED,如果实现的OnTouchListener的onTouch中返回true,并处理事件,则

    if (li != null && li.mOnTouchListener != null
        && (mViewFlags & ENABLED_MASK) == ENABLED
        && li.mOnTouchListener.onTouch(this, event)) {
        //如果满足这些条件那么返回true,这个事件就在此处理
        // 意味着这个View需要事件分发 
        result = true; 
    }

    //如果上一段判断的条件没有满足(没有在代码里面setOnTouchListener的话),就判断View自身的onTouchEvent方法有没有处理,没有处理最后返回false,处理了返回true;
    if (!result && onTouchEvent(event)) {
        result = true;
    }
    
    ...

    return result;
}

dispatchTouchEvent()如果说监听器没有返回true则会执行自己的onTouchEvent()

view#onTouchEvent

public boolean onTouchEvent(MotionEvent event) {

    ...

    final int action = event.getAction();//事件的类型,如down,move,up

    switch (action) {//事件的动作类型
        case MotionEvent.ACTION_UP: // 离开
            ...
                
            performClick();//直接调用PerformClick()方法,执行click
            
            ...
            break;

        case MotionEvent.ACTION_DOWN: //动作:点下
            ...
            break;

        case MotionEvent.ACTION_CANCEL: //动作:取消。所以将所有点击动作还原、清零
            setPressed(false);// 设为为点击
            removeTapCallback();//移除阀门
            removeLongPressCallback();//移除长按阀门
            mInContextButtonPress = false;//上下文按钮点击false
            mHasPerformedLongPress = false;//长按点击未执行
            mIgnoreNextUpEvent = false;//不再忽略下一个事件
            break;

        case MotionEvent.ACTION_MOVE: //动作:滑动(不放开,持续)
            ...
            break;
    }

    return true;//只要发生了点击,长按点击、,上下文点击,就消耗掉事件
    
    //默认不消耗事件  return false;
}

执行onTouchEvent()时,up事件会执行 performClick();

view#performClick

public boolean performClick() {
        // We still need to call this method to handle the cases where performClick() was called
        // externally, instead of through performClickInternal()
        notifyAutofillManagerOnClick();

        final boolean result;
        final ListenerInfo li = mListenerInfo;
    	//查看是否设置点击监听器,只要设置了就一定返回true,没有设置就返回false
        if (li != null && li.mOnClickListener != null) {
            playSoundEffect(SoundEffectConstants.CLICK);
            li.mOnClickListener.onClick(this);
            result = true;
        } else {
            result = false;
        }

        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);

        notifyEnterOrExitForAutoFillIfNeeded(true);

        return result;
    }

所以说在有时候给一个View设置了点击监听器和Touch监听器可能会有冲突,在分发过程中先会去执行TouchListener的事件,不生效才会去执行点击监听器的事件,因此在使用的过程中要注意这个问题

3.事件在ViewGroup的流程

先通读代码,后续对downmove进行详细分析

1.重点函数解析

1.ViewGroup#dispatchTouchEvent
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;
			
        // down事件清除操作
        if (actionMasked == MotionEvent.ACTION_DOWN) {
            //清除上次操作,每次down都意味着从头开始
            cancelAndClearTouchTargets(ev);
            resetTouchState();
        }

        // 拦截标志位
        final boolean intercepted;
        
        //如果是down事件,或者说已经有人消费过了事件就命中if
        if (actionMasked == MotionEvent.ACTION_DOWN
            || mFirstTouchTarget != null) {
            //如果是down事件的话,disallowIntercept为flase,子通过设置这个标志位可以控制父是否有拦截能力
            //down的时候,还没有达到子,所以说子还不能通过调用requestDisallowInterceptTouchEvent()来限制父的能力
            final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
        	//有能力拦截
            if (!disallowIntercept) {
                //上层视图通过调用onInterceptTouchEvent(),判断是否拦截
                intercepted = onInterceptTouchEvent(ev);
                ev.setAction(action); // restore action in case it was changed
            } else {
                //没能力拦截直接为false
                intercepted = false;
            }
        } else {
            //不是down事件而且有人消费过了前面的事件
            intercepted = true;
        }

        //如果拦截或者有人消费过了事件就命中if
        if (intercepted || mFirstTouchTarget != null) {
            ev.setTargetAccessibilityFocus(false);
        }

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

        // Update list of touch targets for pointer down, if needed.
        final boolean isMouseEvent = ev.getSource() == InputDevice.SOURCE_MOUSE;
        final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0
            && !isMouseEvent;
        TouchTarget newTouchTarget = null;
        boolean alreadyDispatchedToNewTouchTarget = false;
        
        //不取消不拦截就执行真正的分发事件
        if (!canceled && !intercepted) {
            //如果事件的目标是可访问性焦点,我们将其交给
            //具有可访问性焦点的视图,如果它不处理它
            //我们像往常一样清除flag,把活动发给所有的children。
            View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
                ? findChildWithAccessibilityFocus() : null;
            
			//down事件才会分发
            if (actionMasked == MotionEvent.ACTION_DOWN
                || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
                //这个事件的索引,也就是第几个事件,如果是down事件就是0
                final int actionIndex = ev.getActionIndex(); // always 0 for down
                 //获取分配的ID的bit数量  多指事件相关后续进行分析
                final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
                    : TouchTarget.ALL_POINTER_IDS;

                 //清理之前触摸这个指针标识,以防他们的目标变得不同步。
                removePointersFromTouchTargets(idBitsToAssign);

                final int childrenCount = mChildrenCount;
                //没有设置Target而且子view的数量不是0进入if
                if (newTouchTarget == null && childrenCount != 0) {
                    //事件的坐标
                    final float x =
                        isMouseEvent ? ev.getXCursorPosition() : ev.getX(actionIndex);
                    final float y =
                        isMouseEvent ? ev.getYCursorPosition() : ev.getY(actionIndex);
                    
                    //view的集合
                    final ArrayList<View> preorderedList = buildTouchDispatchChildList();
                    final boolean customOrder = preorderedList == null
                        && isChildrenDrawingOrderEnabled();
                    final View[] children = mChildren;
                    
                    //轮询view集合
                    for (int i = childrenCount - 1; i >= 0; i--) {
                        final int childIndex = getAndVerifyPreorderedIndex(
                            childrenCount, i, customOrder);
                        //拿到当前view
                        final View child = getAndVerifyPreorderedView(
                            preorderedList, children, childIndex);
                        
                        //判断view能否接收事件
                        //前者判断是否显示或者有动画
                        //后者判断事件是否在view范围内
                        //代码下2
                        if (!child.canReceivePointerEvents()
                            || !isTransformedTouchPointInView(x, y, child, null)) {
                            continue;
                        }
						
                        //判断这个view是否在target链上,也就是之前是否响应过事件
                        //代码3
                        newTouchTarget = getTouchTarget(child);
                        if (newTouchTarget != null) {
                            //如果在这个链上直接把新事件交给这个view就可以了
                            newTouchTarget.pointerIdBits |= idBitsToAssign;
                            break;
                        } 
                        
                        //如果子View不在之前的触摸目标列表中,先重置childView的标志,去除掉CACEL的标志
                        resetCancelNextUpFlag(child);
                        
                        //真正的事件执行方法,后续分析
                        if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                            //如果命中if在,则保留view的信息,view的位置和时间
                            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;
                            }
                            
                            mLastTouchDownX = ev.getX();
                            mLastTouchDownY = ev.getY();
                            //更新当前最新的事件响应view,代码4
                            newTouchTarget = addTouchTarget(child, idBitsToAssign);
                            //准备好分发新事件
                            alreadyDispatchedToNewTouchTarget = true;
                            break;
                        }

                        // 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();
                }
				
                //第一次响应,mFirstTouchTarget不为null,如果这时候又来一个点击事件而且没有view对其进行响应								//newTouchTarget就会为null,将命中if,找到最后一个响应过事件的target,并把这次没有响应的事件交给它
                if (newTouchTarget == null && mFirstTouchTarget != null) {
                    // Did not find a child to receive the event.
                    // Assign the pointer to the least recently added target.
                    newTouchTarget = mFirstTouchTarget;
                    while (newTouchTarget.next != null) {
                        newTouchTarget = newTouchTarget.next;
                    }
                    newTouchTarget.pointerIdBits |= idBitsToAssign;
                }
            }
        }

        //没人消费过事件或者直接拦截,自己处理这个事件
        if (mFirstTouchTarget == null) {
            //真正的处理
            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;
            //遍历target链表
            TouchTarget target = mFirstTouchTarget;
            while (target != null) {
                
                final TouchTarget next = target.next;
                //已经处理过target直接返回true
                if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                    handled = true;
                } else {
                    //是否是取消事件
                    final boolean cancelChild = resetCancelNextUpFlag(target.child)
                        || intercepted;
                    //如果cancelChild为true就取消事件,如果说为cancelChild为false就执行target链上的事件代码1.3
                    //cancelChild为false说明此时可能为一个多点事件,target.child还是之前真正响应过事件的view
                    //这个view再执行一次新的事件
                    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;
            }
        }

        // 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) {
            //如果是多点触摸下的手指抬起事件,就要根据idBit从TouchTarget中移除掉对应的Pointer(触摸点)
            final int actionIndex = ev.getActionIndex();
            final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
            removePointersFromTouchTargets(idBitsToRemove);
        }
    }

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

真正的事件处理函数

private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits) {
        final boolean handled;
 
        // 单独处理ACTION_CANCEL事件
        final int oldAction = event.getAction();
        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;
        }
 
        // Calculate the number of pointers to deliver.
        final int oldPointerIdBits = event.getPointerIdBits();
        final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;
        // 一般情况下,old和new是一样的,而且都是ALL_POINTER_IDS,如果说是多点击事件新旧不同,后续分析
 
        // If for some reason we ended up in an inconsistent state where it looks like we
        // might produce a motion event with no pointers in it, then drop the event.
        if (newPointerIdBits == 0) {
            // 此判断一般不成立
            return false;
        }
 
        final MotionEvent transformedEvent;
    	//单点事件
        if (newPointerIdBits == oldPointerIdBits) {
            // 走这里
            if (child == null || child.hasIdentityMatrix()) {
                // 一般情况下后一个判断为false
                if (child == null) {
                    // 子view为null,调用父类View的dispatchEvent()方法
                    handled = super.dispatchTouchEvent(event);
                } else {
                    // 子view不为null,调整事件在view中的相对位置后再调用子view的dispatchTouchEvent()方法
                    // 但一般这儿走不到,分发给子view是走后面的逻辑
                    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 {
            //1.3多点击事件会对事件进行一个运算处理
            transformedEvent = event.split(newPointerIdBits);
        }
 
        // 1.1 viewGroup继承自view 去执行view的dispatchTouchEvent(),自己拦截则传入的child为空;
        if (child == null) {
            
            handled = super.dispatchTouchEvent(transformedEvent);
        } else {
            //1.2 在这里分发给子view
            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;
    }

3.子view判断是否能响应这个点击

ViewGroup#canReceivePointerEvents

//是否可见,是否有动画
protected boolean canReceivePointerEvents() {
    return (mViewFlags & VISIBILITY_MASK) == VISIBLE || getAnimation() != null;
}

ViewGroup#isTransformedTouchPointInView

//位置是否合理
protected boolean isTransformedTouchPointInView(float x, float y, View child,
                                                PointF outLocalPoint) {
    final float[] point = getTempLocationF();
    point[0] = x;
    point[1] = y;
    transformPointToViewLocal(point, child);
    final boolean isInView = child.pointInView(point[0], point[1]);
    if (isInView && outLocalPoint != null) {
        outLocalPoint.set(point[0], point[1]);
    }
    return isInView;
}
4.子View是否相应过事件

ViewGroup#getTouchTarget

private TouchTarget getTouchTarget(@NonNull View child) {
    for (TouchTarget target = mFirstTouchTarget; target != null; target = target.next) {
        if (target.child == child) {
            return target;
        }
    }
    return null;
}
5.保存返回为true的view

链表头插法

ViewGroup#addTouchTarget

//执行这个方法后,mFirstTouchTarget将不为null
private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
    final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
    target.next = mFirstTouchTarget;
    mFirstTouchTarget = target;
    return target;
}

1.down事件

1.拦截

当用户点击时,ViewGroup会先判断是否拦截,没有则往下分发,此时子view并不能限制ViewGroup的拦截能力,因为子任何事件都没有拿到,触发不了子的任何事件,子没有时机去限制父类,代码都为3.1.1中的片段

//disallowIntercept是子对父的拦截能力的限制,down时一定为false
if (!disallowIntercept) {
    //判断是否拦截,此时假设拦截 为true
    intercepted = onInterceptTouchEvent(ev);
    ev.setAction(action); // restore action in case it was changed
} else {
    //假设后续子对父限制的话,直接为false
    intercepted = false;
}

拦截之后直接走

//没人消费过事件或者直接拦截,自己处理这个事件
if (mFirstTouchTarget == null) {
    //真正的处理 3.1
    handled = dispatchTransformedTouchEvent(ev, canceled, null,
                                            TouchTarget.ALL_POINTER_IDS);
} else {
    //省略
}

这时候传入的childnullcanceledfalse

 if (child == null) {
     // 子view为null,调用父类View的dispatchEvent()方法
     handled = super.dispatchTouchEvent(event);
 }else{
     //省略
 }

super.dispatchTouchEvent(event)则是view的方法,上面 2.事件在view中的流程已进行分析

2.不拦截(目前只讨论单点击,单点事件标志位肯定一开始都为null)

不拦截的话就进行真正的分发,下面代码非完整代码,都为某函数的片段

先看子view有没有能力接收这个事件

//判断view能否接收事件
//前者判断是否显示或者有动画
//后者判断事件是否在view范围内
//代码上3.1.3
if (!child.canReceivePointerEvents()
    || !isTransformedTouchPointInView(x, y, child, null)) {
    continue;
}

有能力处理这个事件再执行

//此时分发事件 代码3.1.2
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
    //省略
}

这时候传入的child不为null

//代码3.1.2
if (child == null) {
    //省略
} else {
    //1.2 在这里分发给子view
    final float offsetX = mScrollX - child.mLeft;
    final float offsetY = mScrollY - child.mTop;
    transformedEvent.offsetLocation(offsetX, offsetY);
    if (! child.hasIdentityMatrix()) {
        transformedEvent.transform(child.getInverseMatrix());
    }
    //执行子的dispatchTouchEvent(),递归调用开始
    handled = child.dispatchTouchEvent(transformedEvent);
}

直到找到一个返回为trueviewnewTouchTargetmFirstTouchTarget赋值

//此时分发事件 代码3.1.1
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
    //省略
    mLastTouchDownX = ev.getX();
    mLastTouchDownY = ev.getY();
    //更新当前最新的事件响应view,代码4
    newTouchTarget = addTouchTarget(child, idBitsToAssign);
    //准备好分发新事件
    alreadyDispatchedToNewTouchTarget = true;
    break;
}

再执行else,因为就一次点击,所以target循环一次就结束,又因为alreadyDispatchedToNewTouchTarget在上边已经赋值为true使得

handled = true

代码3.1.1
if (mFirstTouchTarget == null) {
    //省略
} else {
    //遍历target链表
    TouchTarget target = mFirstTouchTarget;
    while (target != null) {
        final TouchTarget next = target.next;
        //已经处理过target直接返回true,子view执行完就命中这个if
        if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
            handled = true;
        } else {
            //省略
        }
        target = next; 
    }
}

最后返回handled,此次点击事件结束

总结:当分发down事件时,首先看自身拦截否,如果拦截则执行自身的逻辑,如果不拦截,则for循环遍历子view,找到一个处理down事件的view并进行保存,在后面move的时候发挥作用。

2.move事件

move事件中,首先move事件不会再进行分发,一个view如果没有处理down那么一定是处理不了move

//代码3.1.1
//是否分发的条件
if(actionMasked == MotionEvent.ACTION_DOWN
        || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
        || actionMasked == MotionEvent.ACTION_HOVER_MOVE){
    //省略
}

move事件来临

//代码3.1.1
//因为down已经被处理了所以mFirstTouchTarget != null
if (actionMasked == MotionEvent.ACTION_DOWN
    || mFirstTouchTarget != null) {
    //down的时候已经分发过,则子可能会限制父的拦截能力
    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;
}
1.子限制父的拦截能力或者本身就不拦截导致intercepted = false
//代码3.1.1
//因为down有人处理过导致mFirstTouchTarget!=null进入else
if (mFirstTouchTarget == null) {
   //省略
} else {
    TouchTarget predecessor = null;
    TouchTarget target = mFirstTouchTarget;
    //move事件也只循环一次
    while (target != null) {
        final TouchTarget next = target.next;
        //新事件产生的时候会把alreadyDispatchedToNewTouchTarget=false进入else
        if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
            handled = true;
        } else {
            //resetCancelNextUpFlag(target.child)=false,intercepted=false
            final boolean cancelChild = resetCancelNextUpFlag(target.child)
                || intercepted;
            //cancelChild=false,target.child为上次我们处理了down事件的view,所以说把move事件交给了上次处理down事件的view  代码1.2
            if (dispatchTransformedTouchEvent(ev, cancelChild,
                                              target.child, target.pointerIdBits)) {
                handled = true;
            }
            //省略 
        }
        predecessor = target;
        target = next;
    }
}
2.子不限制父的拦截能力且本身要拦截导致intercepted =true

第一次move事件到达

//代码3.1.1
//因为down有人处理过导致mFirstTouchTarget!=null进入else
if (mFirstTouchTarget == null) {
   //省略
} else {
    TouchTarget predecessor = null;
    TouchTarget target = mFirstTouchTarget;
    //move事件也只循环一次
    while (target != null) {
        final TouchTarget next = target.next;
        //新事件产生的时候会把alreadyDispatchedToNewTouchTarget=false进入else
        if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
            handled = true;
        } else {
            //intercepted=true
            final boolean cancelChild = resetCancelNextUpFlag(target.child)
                || intercepted;
            //cancelChild=true,target.child为上次我们处理了down事件的view,所以说把move事件交给了上次处理down事件的view 
            if (dispatchTransformedTouchEvent(ev, cancelChild,
                                              target.child, target.pointerIdBits)) {
                handled = true;
            }
            //cancel则去除当前节点,将mFirstTouchTarget置为null
             if (cancelChild) {
                 if (predecessor == null) {
                     mFirstTouchTarget = next;
                 } else {
                     predecessor.next = next;
                 }
                 target.recycle();
                 target = next;
                 continue;
             }
        }
        predecessor = target;
        target = next;
    }
}

此时在view在执行dispatchTransformedTouchEvent(ev, cancelChild, target.child, target.pointerIdBits)时,cancelchildtrue

//代码3.1.2
// 单独处理ACTION_CANCEL事件
final int oldAction = event.getAction();
//为true进入if
if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
    //设置event为cancel事件
    event.setAction(MotionEvent.ACTION_CANCEL);
    //执行cancel事件
    if (child == null) {
        handled = super.dispatchTouchEvent(event);
    } else {
        handled = child.dispatchTouchEvent(event);
    }
    event.setAction(oldAction);
    return handled;
}

执行完cancel后,第二move来临

//代码3.1.1
//cancle完mFirstTouchTarget == null成立,自己执行move事件
if (mFirstTouchTarget == null) {
    // No touch targets so treat this as an ordinary view.
    handled = dispatchTransformedTouchEvent(ev, canceled, null,
            TouchTarget.ALL_POINTER_IDS);
}

总结:当move时,假设说子view在处理down的时候调用了requestDisallowInterceptTouchEvent()或者说本身不拦截的话(intercepted = false),则会把此次move交给处理了down事件的view处理,假设说move被拦截(intercepted = true),第一次move是先给down view执行cancel事件(mFirstTouchTarget = null),第二次move来临时,就会自己去处理move事件。

up事件比较简单请读者尝试自己分析

3.多指事件

多指事件也有一个先后顺序,事件分发通过链表的形式来处理多指事件(mFirstTouchTarget(链表的头),newTouchTarget(当前最新的target)

此时只分析一种情景,其他情景请读者自己分析

布局如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout android:layout_height="match_parent"
    android:layout_width="match_parent"
    xmlns:android="http://schemas.android.com/apk/res/android">
    <com.hbsd.myviewconstructor.MyTextView
        android:background="@color/black"
        android:layout_width="wrap_content"
        android:layout_weight="1"
        android:layout_height="match_parent"/>
    <com.hbsd.myviewconstructor.MyButton
        android:background="@color/red"
        android:layout_width="wrap_content"
        android:layout_weight="1"
        android:layout_height="match_parent"/>
</LinearLayout>

MyTextView继承自TextViewMyButton继承自Button,只是重写onTouchEvent,监听事件log情况

@Override
public boolean onTouchEvent(MotionEvent event) {
    switch (event.getAction() & MotionEvent.ACTION_MASK) {
        case MotionEvent.ACTION_DOWN:
            Log.e("button down", "first down");
            return true;
        case MotionEvent.ACTION_POINTER_DOWN:
            Log.e("button down", "again down");
            break;
        case MotionEvent.ACTION_POINTER_UP:
            Log.e("button down", "again up");
            break;
        case MotionEvent.ACTION_UP:
            Log.e("button down", "first up");
            break;
    }
    return super.onTouchEvent(event);
}

情景:

用户点击左侧TextView不抬起,再用另一个手指点击右侧Button不抬起,再来一只手指点击左侧TextView不抬起,再来一只手指点击右侧Button不抬起,然后倒着依次抬起。

先讲述原理再分析情景

多指触发的原理

手指头是靠位运算来区分的,比如说第一个手指头为00000001,第二个则为00000010,第三个则为00000100

down的时候,默认为00000001,当后续手指头down,则1往左移当前个手指数,左移即×2

上述所说,只有down事件会分发不准确,还有多指down,也就是ACTION_POINTER_DOWN也会执行分发操作

进入分发,则执行下面代码

//3.1.1
//第几个手指头
final int actionIndex = ev.getActionIndex(); // always 0 for down
//拿到相应的二进制int,split(代表是否支持多指)默认为true,假设是第三个手指,则1左移三位
final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
        : TouchTarget.ALL_POINTER_IDS;

后续执行removePointersFromTouchTargets(idBitsToAssign);

ViewGroup#removePointersFromTouchTargets

private void removePointersFromTouchTargets(int pointerIdBits) {
    TouchTarget predecessor = null;
    TouchTarget target = mFirstTouchTarget; 
    //遍历链表
    while (target != null) {
        final TouchTarget next = target.next;
        //与操作是11为1,其他为0,如果说不为0,则意味着这两个手指重复
        if ((target.pointerIdBits & pointerIdBits) != 0) {
            //与非操作,执行完则target.pointerIdBits = 0
            target.pointerIdBits &= ~pointerIdBits;
            if (target.pointerIdBits == 0) {
                //移除相同元素
                if (predecessor == null) {
                    mFirstTouchTarget = next;
                } else {
                    predecessor.next = next;
                }
                target.recycle();
                target = next;
                continue;
            }
        }
        predecessor = target;
        target = next;
    }
}

上述方法执行完毕则链表中已经移除相同点击手指的target

回到ViewGroup的分发,此时进入循环遍历子View,后续会执行newTouchTarget = getTouchTarget(child);

ViewGroup#getTouchTarget

//寻找相同的child,假设找到则newTouchTarge不为空
private TouchTarget getTouchTarget(@NonNull View child) {
    for (TouchTarget target = mFirstTouchTarget; target != null; target = target.next) {
        if (target.child == child) {
            return target;
        }
    }
    return null;
}

后续再执行

//3.1.1
//子view重复则不为null
if (newTouchTarget != null) {
   	//分析或处理,假设第一个手指是0000 0001,第二个手指是0000 0010,两者或结果为0000 0011,也就代表此view当前有两个手指
    newTouchTarget.pointerIdBits |= idBitsToAssign;
    break;
}

假设上述为null,则进入dispatchTransformedTouchEvent

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

    ...

    //获取全部的点击,假设说此时是第2个手指则值为0000 0011
    final int oldPointerIdBits = event.getPointerIdBits();
    //若此时是第二个手指头则 desiredPointerIdBits为0000 00010,两者与则为0000 00010
    final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;

    if (newPointerIdBits == 0) {
        return false;
    }

    final MotionEvent transformedEvent;
    //若是第二个手指点击则不相等执行else
    if (newPointerIdBits == oldPointerIdBits) {
        //分析多指事件不分析此处
    } else {
        //多指事件的重点
        transformedEvent = event.split(newPointerIdBits);
    }

    // 交给View的分发,执行
    if (child == null) {
        handled = super.dispatchTouchEvent(transformedEvent);
    } else {
        ...
        handled = child.dispatchTouchEvent(transformedEvent);
    }


    return handled;
}

若上述不为null,则再后续的while中进行事件触发

MotionEvent#split

//参数idBits表示当前触摸点
public final MotionEvent split(int idBits) {
    //缓存,类似于Message,安卓中很常见
    MotionEvent ev = obtain();
    synchronized (gSharedTempLock) {
        //获取手指个数
        final int oldPointerCount = nativeGetPointerCount(mNativePtr);
        // 初始化gSharedTempPointerProperties、gSharedTempPointerCoords、gSharedTempPointerIndexMap数组。
        ensureSharedTempPointerCapacity(oldPointerCount);
        final PointerProperties[] pp = gSharedTempPointerProperties;
        final PointerCoords[] pc = gSharedTempPointerCoords;
        final int[] map = gSharedTempPointerIndexMap;
        //获取当前的事件类型
        final int oldAction = nativeGetAction(mNativePtr);
        //必须与ACTION_MASK进行与才能获取真实事件类型
        final int oldActionMasked = oldAction & ACTION_MASK;
        //当前事件的手指索引
        final int oldActionPointerIndex = (oldAction & ACTION_POINTER_INDEX_MASK)
            >> ACTION_POINTER_INDEX_SHIFT;
        //新的手指索引,这里是旧的转换新的,因此会有新的来标记
        int newActionPointerIndex = -1;
        //新的事件触发的手指数
        int newPointerCount = 0;
        //新事件的全部手指
        int newIdBits = 0;
        //遍历全部手指

        for (int i = 0; i < oldPointerCount; i++) {
            //将底层的数据交给pp数组
            nativeGetPointerProperties(mNativePtr, i, pp[newPointerCount]);
            // 获取触摸点ID
            final int idBit = 1 << pp[newPointerCount].id;
            //如果说当前遍历的手指和参数相同或者被包含
            if ((idBit & idBits) != 0) {
                // 该触摸点是TouchTarget感兴趣的
                if (i == oldActionPointerIndex) {
                    // 且该触摸点是引发当前事件的那个触摸点,特别记录下它的索引
                    newActionPointerIndex = newPointerCount;
                }
                // 缓存记录
                map[newPointerCount] = i;
                newPointerCount += 1;
                newIdBits |= idBit;
            }
        }

        // 安全检查
        if (newPointerCount == 0) {
            throw new IllegalArgumentException("idBits did not match any ids in the event");
        }

        // 用于记录事件拆分后新的动作类型
        final int newAction;
        // 仅对ACTION_POINTER_DOWN和ACTION_POINTER_UP进行类型调整
        if (oldActionMasked == ACTION_POINTER_DOWN || oldActionMasked == ACTION_POINTER_UP) {
            if (newActionPointerIndex < 0) {
                // An unrelated pointer changed.
                // 引发当前事件的那个触摸点不是TouchTarget感兴趣的,则将类型调整为
                // ACTION_MOVE,对于该TouchTarget来说,当作普通的滑动事件处理。
                newAction = ACTION_MOVE;
            } else if (newPointerCount == 1) {
                // The first/last pointer went down/up.
                // 引发当前事件的那个触摸点是该TouchTarget感兴趣的,且TouchTarget
                // 感兴趣的个数为1。说明该TouchTarget仅对当前这一个触摸点感兴趣(单点触摸),那么
                // 对于该TouchTarget来说,将是一个全新序列的开始或结束。
                // 将动作类型调整为ACTION_DOWN或ACTION_UP。
                newAction = oldActionMasked == ACTION_POINTER_DOWN
                    ? ACTION_DOWN : ACTION_UP;
            } else {
                // A secondary pointer went down/up.
                // 到了这个case,意味着该触摸点是该TouchTarget上的多点触摸事件,沿用
                // 动作类型,并组合上触摸点索引。
                newAction = oldActionMasked
                    | (newActionPointerIndex << ACTION_POINTER_INDEX_SHIFT);
            }
        } else {
            // Simple up/down/cancel/move or other motion action.
            newAction = oldAction;
        } // 事件动作类型调整完毕

        // 初始化MotionEvent副本
        final int historySize = nativeGetHistorySize(mNativePtr);
        for (int h = 0; h <= historySize; h++) {
            final int historyPos = h == historySize ? HISTORY_CURRENT : h;

            for (int i = 0; i < newPointerCount; i++) {
                nativeGetPointerCoords(mNativePtr, map[i], historyPos, pc[i]);
            }

            final long eventTimeNanos = nativeGetEventTimeNanos(mNativePtr, historyPos);
            if (h == 0) {
                // 使用原对象数据初始化native层对象,并返回对象指针,这里传入了调整后的动作类型。
                ev.mNativePtr = nativeInitialize(ev.mNativePtr,
                                                 nativeGetDeviceId(mNativePtr), nativeGetSource(mNativePtr),
                                                 newAction, nativeGetFlags(mNativePtr),
                                                 nativeGetEdgeFlags(mNativePtr), nativeGetMetaState(mNativePtr),
                                                 nativeGetButtonState(mNativePtr),
                                                 nativeGetXOffset(mNativePtr), nativeGetYOffset(mNativePtr),
                                                 nativeGetXPrecision(mNativePtr), nativeGetYPrecision(mNativePtr),
                                                 nativeGetDownTimeNanos(mNativePtr), eventTimeNanos,
                                                 newPointerCount, pp, pc);
            } else {
                nativeAddBatch(ev.mNativePtr, eventTimeNanos, pc, 0);
            }
        }
        return ev;
    }
}

//作者:dehang0
//链接:https://juejin.cn/post/6844904065617362952
//来源:稀土掘金

上边的方法主要是拆解事件,如何理解呢,举个例子:

比如说第一个手指点击某个View,第二个手指再点击另一个View,此时有两个手指,上述的for会执行两次,两次的手指一个为0000 0001,一个为0000 0010,而传入的参数为当前要执行事件的View的所有手指,对于第二个View来讲则是0000 0010(idBit & idBits) != 0成立则使得newPointerCount + 1 ,此时执行完下面代码,事件由Pointer Down变为Down,返回到dispatchTransformedTouchEvent,执行view的分发方法,此时执行的事件为Down

// 引发当前事件的那个触摸点是该TouchTarget感兴趣的,且TouchTarget
// 感兴趣的个数为1。说明该TouchTarget仅对当前这一个触摸点感兴趣(单点触摸),那么
// 对于该TouchTarget来说,将是一个全新序列的开始或结束。
// 将动作类型调整为ACTION_DOWN或ACTION_UP。
newAction = oldActionMasked == ACTION_POINTER_DOWN
             ? ACTION_DOWN : ACTION_UP;

再对上述方法的for循环进行解释,举个例子:

假设说当前由三根手指,第一根和第三个手指被view1相应,那么此时idBits0000 0101,紧接遍历所有手指,0000 00010000 00100000 0100,对于view1来讲第一个和第三根手指做完与运算,是满足(idBit & idBits) != 0的,此时就会执行两次newPointerCount += 1,最终为2执行下面代码,此case不改变事件,旧actionPointer Down执行后还是Pointer Down

// 到了这个case,意味着该触摸点是该TouchTarget上的多点触摸事件,沿用
 // 动作类型,并组合上触摸点索引。
newAction = oldActionMasked
             | (newActionPointerIndex << ACTION_POINTER_INDEX_SHIFT);
情景分析

用户点击左侧TextView不抬起,再用另一个手指点击右侧Button不抬起,再来一只手指点击左侧TextView不抬起,再来一只手指点击右侧Button不抬起,不再分析手指抬起

上述情景的执行顺序,分析点再split函数,其他方法不进行分析

第一个手指点击

TextView Down

直接触发Down,没有疑问

第二个手指点击
Button Down

此时触发ButtonDown事件,因为执行split,将Ponit Down转换成了Down,走下面分支

// 引发当前事件的那个触摸点是该TouchTarget感兴趣的,且TouchTarget
// 感兴趣的个数为1。说明该TouchTarget仅对当前这一个触摸点感兴趣(单点触摸),那么
// 对于该TouchTarget来说,将是一个全新序列的开始或结束。
// 将动作类型调整为ACTION_DOWN或ACTION_UP。
newAction = oldActionMasked == ACTION_POINTER_DOWN
    ? ACTION_DOWN : ACTION_UP;
TextView Move

此时链表有两个元素,第一个在上面执行了Down事件,此时要执行第二个元素

// An unrelated pointer changed.
// 引发当前事件的那个触摸点不是TouchTarget感兴趣的,则将类型调整为
// ACTION_MOVE,对于该TouchTarget来说,当作普通的滑动事件处理。
newAction = ACTION_MOVE;
第三个手指点击
TextView Ponit Down,

此时TextView 将有两个手指,则走下面分支

// A secondary pointer went down/up.
// 到了这个case,意味着该触摸点是该TouchTarget上的多点触摸事件,沿用
// 动作类型,并组合上触摸点索引。
newAction = oldActionMasked
    | (newActionPointerIndex << ACTION_POINTER_INDEX_SHIFT);
Button Move

和上述分析一样走newAction = ACTION_MOVE;

第四个手指点击

Button Ponit Down

TextView Move

此情况不再分析和第三个手指情况一样

总结:多指事件中重点就是split函数,此函数根据不同情况对事件进行重定向。

原 创 不 易 , 还 希 望 各 位 大 佬 支 持 一 下 \textcolor{blue}{原创不易,还希望各位大佬支持一下}

👍 点 赞 , 你 的 认 可 是 我 创 作 的 动 力 ! \textcolor{green}{点赞,你的认可是我创作的动力!}

⭐️ 收 藏 , 你 的 青 睐 是 我 努 力 的 方 向 ! \textcolor{green}{收藏,你的青睐是我努力的方向!}

✏️ 评 论 , 你 的 意 见 是 我 进 步 的 财 富 ! \textcolor{green}{评论,你的意见是我进步的财富!}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值