android触摸事件的分发

本文根据源码来梳理流程


一、触摸事件传递流程

 用户触摸屏幕,Ativity是最先接触到屏幕的,然后将触摸事件传递到DecorView,然后由DecorView处理具体的事件分发。

 二.ViewGroup和View事件分发逻辑

    ViewGroup的dispatchTouchEvent(MotionEvent ev)执行具体的事件分发。

    1.触摸事件的down动作的执行分发流程。

       首先会去清空mFirstTouchTarget和将当前ViewGroup的事件分发可以执行打断方法的状态标志(即可以执行onInterceptTouchEvent(MotionEvent ev)方法),每个down动作都会清空之前事件保存的状态。

if (actionMasked == MotionEvent.ACTION_DOWN) {
    //清空mFirstTouchTarget
    cancelAndClearTouchTargets(ev);
    //设置mGroupFlags里面可以执行onInterceptTouchEvent()方法的标志位
    resetTouchState();
}

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

如果intercepted==false,ViewGroup不打断事件传递,那么就会像子控件分发down动作。

if (!canceled && !intercepted) {

    // If the event is targeting accessiiblity focus we give it to the
    // view that has accessibility focus and if it does not handle it
    // we clear the flag and dispatch the event to all children as usual.
    // We are looking up the accessibility focused host to avoid keeping
    // state since these events are very rare.
    View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
            ? findChildWithAccessibilityFocus() : null;
    //一定是要down动作或者鼠标停留在Viewgroup才会执行下面代码
    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) {
            final float x = ev.getX(actionIndex);
            final float y = ev.getY(actionIndex);
            // Find a child that can receive the event.
            // Scan children from front to back.
            final ArrayList<View> preorderedList = buildTouchDispatchChildList();
            final boolean customOrder = preorderedList == null
                    && isChildrenDrawingOrderEnabled();
            final View[] children = mChildren;
            //遍历循环
            for (int i = childrenCount - 1; i >= 0; i--) {
                final int childIndex = getAndVerifyPreorderedIndex(
                        childrenCount, i, customOrder);
                final View child = getAndVerifyPreorderedView(
                        preorderedList, children, childIndex);

                // If there is a view that has accessibility focus we want it
                // to get the event first and if not handled we will perform a
                // normal dispatch. We may do a double iteration but this is
                // safer given the timeframe.
                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) {
                    // Child is already receiving touch within its bounds.
                    // Give it the new pointer in addition to the ones it is handling.
                    newTouchTarget.pointerIdBits |= idBitsToAssign;
                    break;
                }

                resetCancelNextUpFlag(child);
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                    // 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;
                    }
                    mLastTouchDownX = ev.getX();
                    mLastTouchDownY = ev.getY();
                    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();
        }

        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;
        }
    }
}
以上代码,在向下分发事件的时候要调用dispatchTransformedTouchEvent()方法,它是真正控制分发事件是自己处理还是对应的子控件来处理。如果交给自己的父类dispatchTouchEvent()处理,就会执行onTouchEvent()方法,onTouchEvent()方法就是处理具体的触摸事件的。onTouchEvent()的执行是有条件的,要根据当前控件是否添加OnTouchListener和OnTouchListener.onTouch()是否消耗了此事件来决定的,可以查看源码了解逻辑。用户的添加的点击事件监听和长按监听也是在onTouchEvent()里面处理的,所以自定义控件重写OnTouchEvent()的时候需要注意。
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
        View child, int desiredPointerIdBits) {
    final boolean handled;

    // Canceling motions is a special case.  We don't need to perform any transformations
    // or filtering.  The important part is the action, not the contents.
    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;

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

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

如果down动作被子控件给消耗掉了(返回true),就会执行下面的代码,会在addTouchTarget()方法里面给mFirstTouchTarget赋值,alreadyDispatchedToNewTouchTarget = true;

if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                    // 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;
                    }
                    mLastTouchDownX = ev.getX();
                    mLastTouchDownY = ev.getY();
                    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();
        }

最后down动作会执行以下代码

if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
    handled = true;
}
最后在dispatchTouchEvent()的最后会返回
return handled;

如果down动作被拦截或者向下分发的时候不消耗此事件,这个时候mFirstTouchTarget == null。这个时候回去执行下面代码

if (mFirstTouchTarget == null) {
    // No touch targets so treat this as an ordinary view.
    handled = dispatchTransformedTouchEvent(ev, canceled, null,
            TouchTarget.ALL_POINTER_IDS);
}

上面child的参数是null,这个时候回去执行父类也就是Viewd的dispatchTouchEvent()方法,最后会调用到自己的onTouchEvent(),这也就是当ViewGroup的子控件不消费这个触摸事件的时候或者当前容器拦截了触摸事件就会执行自己的onTouchEvent()方法。

2.触摸事件的move和up动作的分发流程。

move和up动作在分发流程上大致是一致的,但是不会去遍历子控件查找对应的可以消耗此事件的子控件,这个操作之前的down动作已经做了。如果有子控件消耗了down事件就会将对应的子控件保存在mFirstTouchTarget里面。

当move和up动作在ViewGroup的dispatchTouchEvent()里面开始执行的时候,有两种情况,是根据之前move动作的执行结果而受到影响的,即mFirstTouchTarget是否为空。我们分这两种情况来分析

(1).mFirstTouchTarget == null,即move动作的时候,被viewgroup打断或者子控件不消费此事件。

    开始的时候,如果用户没有修改mGroupFlags的拦截标志位话,默认是会执行onInterceptTouchEvent();

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

因为只有down动作的时候才会去遍历子控件分发分发事件,当是move和up动作的时候newTouchTarget == null,alreadyDispatchedToNewTouchTarget == false;

接下来会执行下面代码

if (mFirstTouchTarget == null) {
    // No touch targets so treat this as an ordinary view.
    handled = dispatchTransformedTouchEvent(ev, canceled, null,
            TouchTarget.ALL_POINTER_IDS);
}

这个时候回去执行父类也就是Viewd的dispatchTouchEvent()方法,最后会调用到自己的onTouchEvent()。

(2).mFirstTouchTarget != null,即ViewGroup的子控件消耗了此事件。

    开始的时候,如果用户没有修改mGroupFlags的拦截标志位话,默认是会执行onInterceptTouchEvent();

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

因为只有down动作的时候才会去遍历子控件分发分发事件,当是move和up动作的时候newTouchTarget == null,alreadyDispatchedToNewTouchTarget == false;

这个时候就会执行如下代码,会将move、up事件通过dispatchTransformedTouchEvent(ev,cancekChild,target.child,target.pointerIdBits)传递个对应的子控件处理或者自己处理。

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;
}
 
 
3.触摸事件的cancel动作的执行流程

    一般情况下cancel事件的产生是由于viewgroup向下分发事件的时候,之前的事件能正常传递到子控件,但是在中途某个事件被某个ViewGroup拦截了,这个VIewGroup要处理这个事件,那么就需要向子控件传递cancel事件,让子控件根据cancel做相应的处理。我们可以看这里的代码

final boolean cancelChild = resetCancelNextUpFlag(target.child)
        || intercepted;
if (dispatchTransformedTouchEvent(ev, cancelChild,
        target.child, target.pointerIdBits)) {
    handled = true;
}
此时cancelChild = true,再到dispatchTransforedTouchEvent()里面看代码。它会修改当前的事件的动作为cancel,然后向下传递给子控件,最后还原当前事件的动作。

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

    // Canceling motions is a special case.  We don't need to perform any transformations
    // or filtering.  The important part is the action, not the contents.
    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;

}

……

总结:

在Viewgroup的dispatchTouchEvent()里面会默认执行onInterceptTouchEvent()方法。用户可以通过调用requestDisallowInterceptTouchEvent()来决定是否执行onInterceptToucheEvent()方法。

1.down事件在Viewgroup的dispatchTouchEvent()里面做了遍历查找是否有子控件能消费此事件,如果消费了此事件就保存子控件,如果没有就自己消费,具体消费就是将触摸事件传递给自己的onTouchEvent()。

2.move和up事件在Viewgroup的dispatchTouchEvent()里面直接根据有没有子控件消费之前的down事件来来判断是将事件传递给自己的onTouchEvent()还是传递给子控件的dispatchTouchEvent().

3.ViewGroup和View的dispatchTouchEvent()又要查看源码。

4.ViewGroup的具体事件分发是由dispatchTransformedTouchEvent()来完成的。


猜你喜欢

转载自blog.csdn.net/u010695063/article/details/80674746