Android-ViewGroup的事件分发

伪代码

boolean dispatchTouchEvent(Event event){
    boolean result = false;

    //第一步重置
    if(DOWN){
        //DOWN事件中重置。如清空FirstTarget,将禁止拦截标志位重置为0
    }

    //第二步判断是否要拦截
    if(DOWN || firstTarget != null){//firseTarget有值,代表事件曾经分发到了子View
        if(禁止拦截标志位为0){ //禁止拦截标志位可以由子View设置为1
            intercepted = onInterceptTouchEvent();//询问自己是否要拦截
        }else {
            intercepted = false;
        }
    }else {
        intercepted = true;
    }

    //第三步 DOWN事件中的分发判断
    if(!intercepted){
        if(DOWN){
            for(int i = childCount-1; i>=0; i--){ //倒着循环,所以访问的是最后添加的View,即最上层显示的View
                View v = childs[i];
                if(v在触摸范围内){
                    if(v.dispatchTouchEvent(event)){
                        addTarget(v);//将该v加入firstTarget链表中,之所以是个链表因为有多指情况,单指不考虑
                        alreadyDispatchedToNewTouchTarget = true;//用于第四步避免重复分发
                        break;
                    }
                }
            }
        }
    }

    //第四步 之后事件的分发判断
    if(firstTarget == null){//当无子View消耗事件
        result = super.dispatchTouchEvent(event);//把自己当成一个View,事件交给自己判断
    }else{
        target = firstTarget;
        while(target != null){//对于单指情况,只循环一次
            if(alreadyDispatchedToNewTouchTarget){
                result = true;
            }else {
                v = target.view;
                boolean cancelChild = intercepted || v是否需要cancel;
                if(cancelChild){
                    event.setAction(CANCEL);
                }
                result = v.dispatchTouchEvent(event);

                if(cancelChild){
                    从firstTarget链表中移除该v所在的target;
                }
                target = target.next;
            }
        }
    }

    return result;
}

}
···

代码细节
1、mFirstTarget的用处:用于"非DOWN事件"的直接分发。在DOWN事件中会通过循环来询问子View是否消耗事件,如果消耗,mFirstTarget就会有值,后续时间就会通过第四步直接对其进行分发。如果该值为空,说明无子View会处理事件,则第四步就会交给自己处理。
2、alreadyDispatchedToNewTouchTarget变量,是为了第三步已经分发了,而在第四步重复分发的情况,该变量在非DOWN事件就失去作用了。

结论
1、先判断子View是否消耗事件,如果子View不消耗才交由自己消耗。
在DOWN事件中会通过循环来访问子View,如果消耗,则mFirstTarget有值,\color{#FF0000}{DOWN之后}的事件通过mFirstTarget对目标子View直接进行分发;如果子View都不消耗,则mFirstTarget无值,\color{#FF0000}{DOWN及之后}事件交由自己判断。

2、决定拦截后,后续事件交由自己处理
如果DOWN事件中拦截,则mFirstTarget无值,所以直接交由自己处理
如果非DOWN事件中拦截,则mFirstTarget有值,此时,在第四步中当前事件会清空mFirstTarget中的Target,并对该Target中的子View发送CANCEL事件,而后续事件mFirstTarget等同于无值处理(即交由自己处理)。并且之后在第二步也不会再询问是否要阻止拦截,直接认为拦截事件。

3、子View显示在上层的会先判断触摸事件
因为第三步的循环时根据子View的添加顺序逆序循环,而最后添加的子View都是显示在最上层,所以如果最上层子View消耗了事件,被遮挡的子View就无法收到事件了

4、子View无法在DOWN事件中阻止其父View拦截事件(如果其父View想要拦截的话)
因为子View如果想阻止拦截,是通过设置FLAG的方式,然后在第二步会进行判断。但是第一步DOWN事件会重置状态。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。