事件分发的主要三个方法就是
public boolean disptatchTouchEvent(MotionEvent ev);
public boolean onIterceptTouchEvent(MotionEvent ev);
public boolean onTouchEvent(MotionEvent ev);
大致流程用伪代码描述如下
public boolean dispatchTouchEvent(MotionEvent ev){
boolean cousume =false;
if( onInterceptTouchEvent(ev)){
consume = onTouchEvent(ev);
}else {
consume = child.dispatchTouchEvent(ev);
}
return consume;
}
1,ViewGroup 的 onInterceptTouchEvent() 默认返回false,即不拦截触摸事件.
2,一次触摸事件总是以 MotionEvent.ACTION_DOWN 开始,中间可能有多个
3,MotionEvent.ACTION_MOVE, 最后以 MotionEvent.ACTION_UP结束.一次触摸事件通常被一个view 接受并处理掉;
final int action = ev.getAction();
//事件分发总是以down事件开始
if (actionMasked == MotionEvent.ACTION_DOWN) {
// mFirstTouchTarget表示一次事件分发中,由处理事件的子view组成的单链表
mFirstTouchTarget.clear();
mFirstTouchTarget=null;
//重置FLAG_SISALLOW_INTERCEPT 标记
disallowIntercept=false;
}
//是否拦截,final修饰,表示只能赋值一次
final boolean intercepted;
//接下来是判断是否需要拦截事件
if (action == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {
//这是上文清除的那个标记,子view调用requestDisallowInterceptTouchEvent 来阻止父类拦截触摸事件
//在上边已经将它至为了false
if(!disallowInterctpt){
intercepted =onIterceptTouchEvnet(ev);
}eles{
intercepted = false;
}
}else{
//viewgroup来处理本次触摸事件
intercepted = false;
}
讲解,在最开始就把 mFirstTouchTarget 和 disallowIntercept 都重置,所
以第一次,所以会执行 onIterceptTouchEvnet(ev)方法,来决定是否拦截,
1,如果onIterceptTouchEvnet返回true,那么 mFirstTouchTarget就为null , 那么down之后的move,up就都交给 本viewgroup来处理,不会再走onIterceptTouchEvnet()方法;
2.如果onIterceptTouchEvnet 返回 false,表示不拦截,就会在下边让mFirstTouchTarget得到处理触摸事件的view,最后的move,up事件都会走 vp的onIterceptTouchEvnet 判断师傅拦截.
3.view 调用requestDisallowInterceptTouchEvent() 阻止父类拦截down以外的所有事件.
接着看代码
//是否是anciton_cancel
final boolean canceled = action==MotionEvent.ACTION_CANCEL;
//是否完成了事件分发
boolean alreadyDispatchedToNewTouchTarget = false;
//最先接受事件分发的子view对于的targert
//TouchTarget 将 view 和触摸事件的 idbit 联系起来,同时他还是一个链表的节点结构,由一个 public TouchTarget next; 成员
TouchTarget newTouchTarget = null;
//这里是事件分发的代码,不是取消事件,也没被拦截,就需要进行分发
if (!canceled && !intercepted) {
if(action=MotionEvent.ACTION_DOWN){
//idBit时触摸点的id,是为了区分多点触控和单点触控的标志
final int idBitsToAssign=event.getPointId();
//获得vp的子view的个数
final int childrenCount = mChildrenCount;
//有子view但还没有子view处理触摸事件
if (newTouchTarget==null&&childrenCount != 0) {
//拿到具体触摸事件的坐标,相对于vp本身的坐标
final float x = ev.getX();
final float y = ev.getY();
//得到一个处理事件分发的子view的排序集合 z坐标由小到大-触摸事件完成后这个集合就会销毁
final ArrayList<View> preorderedList =buildTouchDispatchChildList();
//mChildren 是vp的子view的数组集合
final View[] children = mChildren;
///遍历从后向前得到view(z坐标从大向小)
for (int i = childrenCount - 1; i >= 0; i--) {
//这个方法其实就是 preorderedList不为空,就从preorderedList里得到view,为空就在 childern 里得到view
final View child=getAndVerifyPreorderedView(preorderedList, children,childIndex);
//child能否接受点击事件-child可见,或正在执行动画,且触摸坐标在动画范围里
if (!canViewReceivePointerEvents(child)|| !isTransformedTouchPointInView(x, y, child, null)) {
continue;
}
//到这里时,表示这个child 可以处理触摸事件
//找到child关联的touchtarget,第一次肯定为null
newTouchTarget = getTouchTarget(child);
if (newTouchTarget != null) {
//child 接受了点击事件,把这次点击的 事件id 交给child的touchtarget
//相当于事件被传递到子view
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}
上述代码就是以view视图的z坐标进行遍历,找到触摸事件的坐标所处的那个view,将触摸事件的pointerId交个这个view对应的ToiuchTarget,这样,这个vew和触摸事件就建立起来了关联.同时跳出循环
接着看代码
//这个方法是真正进行事件分发的地方,如果第三个参数 child 不为空,就由调用
//child的dispatchTouchEvent(event);方法,为空的话,就调用
super.dispatchTouchEvent(event);交给了vp的父类处理,
//为什么要交给父类?因为上边提到了 if (!canceled && !intercepted)
//表示vp不拦截事件,即vp自己不处理,而子view也不处理的话,就把事件交给vp的
//上一层 这个方法的返回结果表示是否处理了事件,第二个参数表面是否
// action==MotionEvent.ACTION_CANCEL,最后的参数是 本次的pointerId,
//这个方法里还同样考虑的多点触摸的处理..不过没看懂
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
//新建一个touchTarget,关联上child,pointerId,并把它连接到
//mFistTouchTargt的单链表上,作为最先处理本次触摸事件剩下的
//up,move事件的view
final TouchTarget target = TouchTarget.obtain(child, idBitsToAssign);
target.next = mFirstTouchTarget;
mFirstTouchTarget = target;
//表示完成了事件分发
alreadyDispatchedToNewTouchTarget = true;
break;
}
}//这是遍历view的结束框
//销毁z坐标排序的view序列,防止内存泄漏
if (preorderedList != null) preorderedList.clear();
}
//如果本次遍历vp的子view没有找到处理事件的((newTouchTarget == null )
//就从之前的处理touch的列表中找到合适的view,把触摸事件和他相关联
//进入这里的情况应该是上边
dispatchTransformedTouchEvent时,child进行了事件分发,但是返回了false,即子view能接受到触摸事件却没处理,就只能找到他上层的view处理
if (newTouchTarget == null && mFirstTouchTarget != null) {
newTouchTarget = mFirstTouchTarget;
while (newTouchTarget.next != null) {
newTouchTarget = newTouchTarget.next;
}
newTouchTarget.pointerIdBits |= idBitsToAssign;
}
}
//接下来,这里是move,up 事件的处理
if (mFirstTouchTarget == null) {
//没有子view要处理,就调用super.dispatchTouchEvent(),来处理
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
}else{
target=mFistTouchTarget;
while(target !=null){
final TouchTarget next = target.next;
if (alreadyDispatchedToNewTouchTarget && next == newTouchTarget) {
handled = true;
}else{
//遍历调用mFirstTargetTouchEvent的view和view对应的pointId 进行事件分发
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
target=next;
}
}
}
//对up事件的处理,就是重置所有状态 mFirstTouchTarget至为空
if (canceled|| actionMasked == MotionEvent.ACTION_UP) {
mFirstTouchTarget.clear;
mFirstTouchTarget=null;
}else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
//应该是得到本次 pointer_up 对应的pointerId
final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
//遍历 mFristTouchTarget,把和这个id在target中出去
removePointersFromTouchTargets(idBitsToRemove);
}
return handled;//返回分发的结果,到这里,分发事件结束
}
所以,大致流程还是先判断是否要拦截.不拦截的话.先处理 down事件.通过down事件的处理找到子view中能进行事件处理的view,无论有或者没有, 那么之后的move,up事件就可以通过mFirstTouchTarget来决定如何进行分发,不过这里代码比较麻烦的地方是掺杂了多点触控(idBitsToAssign),及cancel事件的特殊处理,和辅助功能相关的(Accessibility,这部分没有列出来).我也是看了三遍.才明白.通过这次笔记,才最终彻底理清脉络.
接下来看 onInterceptTouchEvent
//通过实现这个方法,你可以观测到事件如何在子类中进行分发-需要返回false
//无论如何,down事件都会进这个方法,而且down要么被child消耗,要么被vp消耗
//onTouchEvnet()返回true后,move和up事件就不会进入这个方法,而是直接走
//ouTouchEvent,如果你返回了false,那么之后的up,move,都会进入这个方法,在走到子
//view的onTouhEvent()中.
//这些注释是本来这个方法上的注释的摘抄
public boolean onInterceptTouchEvent(MotionEvent ev) {
//这些没看懂,但是应该是基本不会发生
if (ev.isFromSource(InputDevice.SOURCE_MOUSE)
&& ev.getAction() == MotionEvent.ACTION_DOWN
&& ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY)
&& isOnScrollbarThumb(ev.getX(), ev.getY())) {
return true;
}
return false;
}
至于onTouchEvent(); viewgropu 并没有实现这个方法,vp继承自view,所以直接看view的onTouchEvent()方法就好了. 不在本篇讨论范围,等我的view事件分发代码详解在写.
果然.讲出来是学习最快的方法.通过这个博客才最终理解了viewgroup的分发原理.及里边的一些细节的用处.