Android事件分发机制小结


基本知识

事件传递的三个主体

Activity、ViewGroup、View
他们三个的嵌套关系通常是这样:

可是还要明白的是:


java

  • ViewGroup固然能够嵌套ViewGroup,即ViewGroup也能够是另外一个ViewGroup的子View。
  • ViewGroup实际上是继承于View,是View的子类。

事件分发机制相关三个经典函数

  • dispatchTouchEvent():分发函数
  • onInterceptTouchEvent():拦截函数
  • onTouchEvent():消费函数

它们的功能和它们名字同样。其中拦截函数是ViewGroup独有的,其它两个函数在上面说的三个主体都存在。函数

事件分发机制四个经典事件

  • ACTION_DOWN
  • ACTION_MOVE
  • ACTION_UP
  • ACTION_CANCLE

意如其名源码分析

事件分发机制场景

参考文章:https://blog.csdn.net/caifengyao/article/details/65437695?utm_medium=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.channel_param&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.channel_param学习

不拦截、不消费

当三个主体的任何函数的 返回值 都不作任何处理时,即不拦截、不消费:动画


可见:
ui

  • 对于down事件:我会从外层一层层地分发下去(Activity->ViewGroup->view),看看
  • down不消费,move,up我就不传递去了

ViewGroup拦截、无消费

当在ViewGroup使onInterceptTouchEvent()返回true,即ViewGroup对事件进行拦截时:this


可见:
spa

  • 事件被拦截以后就不会往下分发

ViewGoup消费,不拦截

当在ViewGroup使onTouchEvent()返回true,即ViewGroup对事件进行消费时:


可见:


.net

  • 当down被消费了就不会往上冒
  • move up不会往下发,而是直接分发给消费者。

源码分析

具体代码怎么实现?主要是看分发函数dispatchTouchEvent(),接下来咱们看看 三个主体的dispatchTouchEvent() 源码分析code

Activity的dispatchTouchEvent()源码

public boolean dispatchTouchEvent(MotionEvent ev) { 
	if (ev.getAction() == MotionEvent.ACTION_DOWN) { 
	    onUserInteraction();//这是一个空方法
	}
	//主要看这一句
	//getWindow().superDispatchTouchEvent(ev)
	//这句函数调用的时DecorView的superDispatchTouchEvent()
	//而DecorView继承于ViewGroup
	if (getWindow().superDispatchTouchEvent(ev)) { 
	    return true;
	}
	return onTouchEvent(ev);
}

代码中我写了注释,咱们能够得出下面的结论:

  • getWindow().superDispatchTouchEvent(ev),实际是调用了一个ViewGroup的dispatchTouchEvent()
  • getWindow().superDispatchTouchEvent(ev)返回true,说明有子View消费该事件(为何呢?咱们要分析完ViewGroup的dispatchTouchEvent()才知道,但如今能够暂时给出这个结论);这个子View多是某个ViewGroup或者View。
  • 若是有子view消费该事件则返回true,不然调用自身的onTouchEvent(ev),即把事件分发给本身。

ViewGroup的dispatchTouchEvent()源码

ViewGroup的dispatchTouchEvent()源码很长,我参考了https://blog.csdn.net/wolinxuebin/article/details/53057075
以后得出一些小结,如今贴出一部分,一段一段分析。

整体逻辑分析

我把部分代码和具体逻辑去掉,看它的空架子

//这是一个单链表,我暂时理解为用于存放响应了DOWN的事件
private TouchTarget mFirstTouchTarget;

public boolean dispatchTouchEvent(MotionEvent ev) { 
	...
	//判断是不是模糊窗口,若是是窗口,则表示不但愿处理改事件。(如dialog后的窗口)
	if (onFilterTouchEventForSecurity(ev)) { 
	    // 清空以前的状态
	    if (actionMasked == MotionEvent.ACTION_DOWN) { }
	
	    //检查是否须要拦截
	    final boolean intercepted;
	    if (actionMasked == MotionEvent.ACTION_DOWN
	            || mFirstTouchTarget != null) { } else { }
	            
	    //检查是否要取消,即标记了PFLAG_CANCEL_NEXT_UP_EVENT 或者 当前是一个Cancel事件
	    final boolean canceled = resetCancelNextUpFlag(this)
	            || actionMasked == MotionEvent.ACTION_CANCEL;
	
	    //不用取消,无需拦截,则进行事件分发 
	    if (!canceled && !intercepted) { 
	       //分发DOWN事件
	        if (actionMasked == MotionEvent.ACTION_DOWN
	                || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
	                || actionMasked == MotionEvent.ACTION_HOVER_MOVE) { 
	           //分发DOWN给child
	            if (newTouchTarget == null && childrenCount != 0) {  
	                
	            }
	            
	            //若是没有child相应该事件,则将此事件交给最近加入的target?
	            //这里不是很懂,若是没有child响应,那么mFirstTouchTarget也是null呀
	            if (newTouchTarget == null && mFirstTouchTarget != null) { 
	               
	            }
	        }
	    }
	
	    //mFirstTouchTarget为空代表没有child响应这个事件,则分发给本身
	    if (mFirstTouchTarget == null) { }
	    //按照mFirstTouchTarget分发
	    else { }
   
    }
	...
	//若是自身或者child消费了事件则返回true,不然返回false
	return handled;    
}

关于解析看代码中的注释。接下来看看其中几段逻辑具体怎么实现的。

具体分析一

看一下分发给DOWN给子View的具体逻辑:

//分发DOWN给child
if (newTouchTarget == null && childrenCount != 0) {  
	
	// 对子Views进行排序,有两种方式:一、按照Z轴,二、按照draw
	final ArrayList<View> preorderedList = buildTouchDispatchChildList();
	final boolean customOrder = preorderedList == null
	        && isChildrenDrawingOrderEnabled();
	final View[] children = mChildren;

	//遍历子View
	for (int i = childrenCount - 1; i >= 0; i--) { 
		//这里两行代码,简单的理解根据不一样的排列选项(一、view添加到 二、view的draw顺序 三、viewZ 轴顺序)
	    final int childIndex = getAndVerifyPreorderedIndex(
	            childrenCount, i, customOrder);
	    final View child = getAndVerifyPreorderedView(
	            preorderedList, children, childIndex);
	            
		//canViewReceivePointerEvents 判断child是否为visiable 或者 是否有动画
        //isTransformedTouchPointInView 判断x, y是否在view的区域内(若是是执行了补间动画 则x,y会经过获取的matrix变换值
        // 换算当相应的区域,这也是为何补间动画的触发区域不随着动画而改变)
	    if (!child.canReceivePointerEvents()
	            || !isTransformedTouchPointInView(x, y, child, null)) { 
	        continue;
	    }
	    
		//若是chile已经在mFirstTouchTarget单链表里面,结束循环
	    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;
	    }
	
		//判断child的dispatchTouchEvent()是否会返回true,若是是true,将child加入单链表,而后结束循环
		//dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)会调用child的dispatchTouchEvent()
	    resetCancelNextUpFlag(child);
	    if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) { 
	        // Child wants to receive touch within its bounds.
	        mLastTouchDownTime = ev.getDownTime();
	        if (preorderedList != null) { 
	            // 找到childIndex所表明的child的最原始的index
	            for (int j = 0; j < childrenCount; j++) { 
	                if (children[childIndex] == mChildren[j]) { 
	                    mLastTouchDownIndex = j;
	                    break;
	                }
	            }
	        } else { 
	            mLastTouchDownIndex = childIndex;
	        }
			
			//将相应该事件的child包装成一个Target,添加到mFirstTouchTarget链表中
	        mLastTouchDownX = ev.getX();
	        mLastTouchDownY = ev.getY();
	        newTouchTarget = addTouchTarget(child, idBitsToAssign);
	        alreadyDispatchedToNewTouchTarget = true;
	        break;
	    }
	}
}

这里我只贴出部分的代码,解析都在注释,能够看到,mFirstTouchTarget链表只存在一个值,就是响应了事件的那个child。

具体分析二

//这一段的要么分发down给本身要么按照单链表分发move、up
//伪代码
// 
if (mFirstTouchTarget == null) { 
    //没有child响应事件,则分发给本身,handled将做为返回值返回。
    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;
        //在前面Down事件处理中,已经将这个事件交给newTouchTarget处理过了,就不重复处理了
        if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) { 
            handled = true;
        } else { 
        	//这里实际上是分发move和up事件,由于down事件在前面已经处理完了,不会进入这里
        	//再次断定是否须要cancel,由于有可能在move过程事件被拦截
            final boolean cancelChild = resetCancelNextUpFlag(target.child)
                    || intercepted;
            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的dispatchTouchEvent()源码

一样的,我省略了部分代码,解析看注释

public boolean dispatchTouchEvent(MotionEvent event) { 
	boolean result = false;
	
	if (onFilterTouchEventForSecurity(event)) { 
	    //若是设置了onTouchListener,会先调用onTouch()
	    ListenerInfo li = mListenerInfo;
	    if (li != null && li.mOnTouchListener != null
	            && (mViewFlags & ENABLED_MASK) == ENABLED
	            && li.mOnTouchListener.onTouch(this, event)) { 
	        result = true;
	    }
	
		//调用onTouchEvent()
	    if (!result && onTouchEvent(event)) { 
	        result = true;
	    }
	}
	return result;
}

能够看到:

  • View的dispatchTouchEvent()的返回值取决于onTouch()或onTouchEvent(),也就是有没有消费该事件。
  • 若是onTouch()返回true的话就不会调用onTouchEvent(),这就是为何有些博客写onTouch()优先于onTouchEvent()。

总结

分析源码就能够知道前面说的三种场景是怎么回事了。

有点像皇帝派任务,皇帝说如今有个好活儿,可是不知道谁要接这个活儿,因而派了一个小太监去探测一下;
小太监先去找宰相,宰相又让他去找知府,知府让他去找衙门小兵。

这里面皇帝就像Acticity、各级官员就像ViewGroup、小兵就像View、而小太监就像DOWN事件,活儿就是跟着DOWN后面的MOVE和UP事件。

  • 不拦截、不消费:小太监一层层找到小兵后,没有一个小兵想接这个活儿(一层层分发DOWN事件),因而小兵沿路返回报告给知府、知府也不想作就报告给宰相,宰相不想作就回去报告给皇帝,皇帝说没人作那我看看本身能不能作吧(DOWN事件回到Activity派给本身,MOVE、UP也再也不分发而是直接派给本身)
  • ViewGroup拦截、不消费:宰相让小太监找到知府的时候, 这个知府有点霸道直接把小太监拦下了,小太监就就再也不继续通知下级人员了(拦截事件)。可是呢这个官员只是单纯拦下了小太监但他并不想接这个活儿,因而小兵仍是沿路回去报告给说下面没人接活,最终仍是传回给皇帝说没人作那我看看本身能不能作(MOVE、UP再也不分发直接派给本身)
  • ViewGroup消费、不拦截:一样,无人拦截的话,小太监一层层找到小兵,发现小兵没人想作,就回去报告知府,这时候知府说小兵不作我来作(DOWM在这里被消费了)。而后知府写信报告宰相说这活儿我接了(返回true),宰相又报告皇帝说下面有人接受任务了,然会皇帝下次就直接派发任务给宰相,宰相找到那个愿意接受任务知府,把任务派给他。(派发MOVE、UP)

分发函数分发DOWN时其实有点相似于递归的方式,只不过不是本身调用本身,而是一层层地调用child的同名函数。分发MOVE、UP则再也不一一询问,而是根据DOWN是否被消费进行分发。

源码很长如今头都有点乱,那么从源码能够学习到什么呢,你们帮忙补充吧

  • 若有有这种嵌套式的应答需求,能够学习ViewGroup的分发函数,用相似于递归的方式提问和接收应答。
  • 对于较大量的信息,命令传送,能够先派一个小兵嗅探一下,记录可行的路线,后续信息按路线分发。
相关文章
相关标签/搜索