触摸事件(Touch)源码分析

触摸事件分发 一般可以说是从activity开始,经过PhoneWindow到DecorView,再在ViewTree上进行分发。

如果要更深一步了解触摸事件怎么到activity的,可以看以下文章

1、Android 输入事件一撸到底之源头活水(1)
2、Android 输入事件一撸到底之DecorView拦路虎(2)
3、Android 输入事件一撸到底之View接盘侠(3

触摸事件从activity到DecorView源码

    //activity中
    public boolean dispatchTouchEvent(MotionEvent ev) {
        ……
        //getWindow()就是拿到了PhoneWindow
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        return onTouchEvent(ev);
    }

    //PhoneWindow中
    @Override
    public boolean superDispatchTouchEvent(MotionEvent event) {
        //mDecor就是DecorView
        return mDecor.superDispatchTouchEvent(event);
    }

    //DecorView中
    public boolean superDispatchTouchEvent(MotionEvent event) {
        //调用ViewGroup的事件分发
        return super.dispatchTouchEvent(event);
    }

过程比较简单,其实在DecorView中复写了dispatchTouchEvent方法,而activity到DecorView的过程中并没有调用到,如果想知道具体原因,可以看上面的第二篇文章。

由简入繁,先分析单点触摸事件中有ACTION_DOWN,ACTION_MOVE,ACTION_UP
在没有任何特殊处理下,我们知道在ACTION_DOWN事件时,会找到消费触摸事件的视图,然后ACTION_MOVE,ACTION_UP的时候会沿着原来的路径传到该视图让其继续消费。

down事件怎么形成虚拟路径

关键在dispatchTouchEvent和dispatchTransformedTouchEvent代码中
去除其他的无关的,只分析一般情况

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
            
            if (actionMasked == MotionEvent.ACTION_DOWN) {
                //down事件的一些初始化和重置
                cancelAndClearTouchTargets(ev);
                resetTouchState();
            }

            // 检测是否拦截
            final boolean intercepted;
           ……
            //一般情况,非拦截,非取消
            if (!canceled && !intercepted) {

                if (是down事件) {
                    final int childrenCount = mChildrenCount;
                    if (newTouchTarget == null && childrenCount != 0) {
                        //后add进来的view开始遍历
                        for (int i = childrenCount - 1; i >= 0; i--) {
                            final int childIndex = getAndVerifyPreorderedIndex(
                                    childrenCount, i, customOrder);
                            final View child = getAndVerifyPreorderedView(
                                    preorderedList, children, childIndex);

                            if (如果子view不接收触摸事件,或者点击范围不在子view区域) {
                                continue;
                            }

                            //非常关键的一步:
                            //这个方法中会先调用子View的dispatchTouchEvent,是一种责任链的模式   
                            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                                //能进来,说明子View或者孙子View消费了事件
                                //也是非常关键的一步:这里赋值了mFirstTouchTarget
                                newTouchTarget = addTouchTarget(child, idBitsToAssign);
                                alreadyDispatchedToNewTouchTarget = true;
                                break;
                            }
                        }
                    }
                }
            }

            if (mFirstTouchTarget == null) {
                // 说明没有子View或者孙子View消费了触摸事件
                // 也是子View的touch没有消费事件,事件又往parent的Touch传的表现
                handled = dispatchTransformedTouchEvent(ev, canceled, null,
                        TouchTarget.ALL_POINTER_IDS);
            } else {
               
                TouchTarget target = mFirstTouchTarget;
                while (target != null) {
                   // alreadyDispatchedToNewTouchTarget为true,说明是点击事件
                    if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                        handled = true;
                    } else {
                  //否者就move或者up事件
                        
                        if (dispatchTransformedTouchEvent(ev, cancelChild, target.child, target.pointerIdBits)) {
                            handled = true;
                        }
                    }
                }
            }

        return handled;
    }


    private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits) {
        final boolean handled;
        ……
        if (child == null) {
            //如果child为null,就调super,说明这个ViewGroup当成view来处理分发事件
            handled = super.dispatchTouchEvent(transformedEvent);
        } else {
            //调用子View的dispatchTouchEvent,直接决定了dispatchTransformedTouchEvent的返回值
            handled = child.dispatchTouchEvent(transformedEvent);
        }
        return handled;
    }

假如viewTree的关系如下

     A(父) ->  B1、B2、B3
    B2 (父)-> C1、C2、C3

假设C2消费了down事件,事件分发的路径是 A -> B2 -> C2

可以看到,在B2的dispatchTouchEvent中的这段代码里,
会创建mFirstTouchTarget,同样A中也是

if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
    newTouchTarget = addTouchTarget(child, idBitsToAssign);
    alreadyDispatchedToNewTouchTarget = true;
    break;
}

所以在down事件结束后,A 和 B2里的mFirstTouchTarget都被创建,而TouchTarget中的View正是child,即A中的mFirstTouchTarget里持有了B2。B2中的mFirstTouchTarget里持有了C2
这样,一个虚拟的事件分发链路就出现了。

在move事件中,就直接走这段代码里的else逻辑,直接从touchtarget中获取child

TouchTarget target = mFirstTouchTarget;
while (target != null) {
    // alreadyDispatchedToNewTouchTarget为true,说明是点击事件
    if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
        handled = true;
    } else {
    //否者就move或者up事件
        if (dispatchTransformedTouchEvent(ev, cancelChild, target.child, target.pointerIdBits)) {
            handled = true;
        }
    }
}

cancel事件什么时候会被触发

看下这样的布局:

activity里放一个FrameLayout1,
FrameLayout1里放一个FrameLayout2,
FrameLayout2里放一个View1

down事件让View1消费,move事件被FrameLayout1拦截,打印出日志如下,
可以看到FrameLayout2和View1都出现了cancle

activity: dispatchTouchEvent ACTION_DOWN
FrameLayout1: dispatchTouchEvent ACTION_DOWN
FrameLayout1: onInterceptTouchEvent ACTION_DOWN
FrameLayout2: dispatchTouchEvent ACTION_DOWN
FrameLayout2: onInterceptTouchEvent ACTION_DOWN
View1: dispatchTouchEvent ACTION_DOWN
View1: onTouchEvent ACTION_DOWN
activity: dispatchTouchEvent ACTION_MOVE
FrameLayout1: dispatchTouchEvent ACTION_MOVE
FrameLayout1: onInterceptTouchEvent ACTION_MOVE
FrameLayout2: dispatchTouchEvent ACTION_CANCEL
View1: dispatchTouchEvent ACTION_CANCEL
View1: onTouchEvent ACTION_CANCEL

主要的代码在处理move事件那里:

final boolean cancelChild = resetCancelNextUpFlag(target.child) || intercepted;
if (dispatchTransformedTouchEvent(ev, cancelChild, target.child, target.pointerIdBits)) {
    handled = true;
}

拦截后,cancelChild为true,然后在dispatchTransformedTouchEvent中

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;
}
  1. 如果是View,调用dispatchTouchEvent和onTouchEvent
  2. 如果是ViewGroup,则调用ViewGroup的dispatchTouchEvent,里边的canceled为true,最终调resetTouchState清除mFirstTouchTarget。

你可能感兴趣的:(触摸事件(Touch)源码分析)