View的事件传递详细过程

2017-09-09  本文已影响0人  一刀牛

一、先分析下Activity里面的事件传递过程

 public boolean dispatchTouchEvent (MotionEvent ev) {
    if (ev.getAction() == MotionEvent. ACTION_DOWN) {
       onUserInteraction();                          
    }
    
    if (getWindow().superDispatchTouchEvent(ev)) {  
        return true; 
    }
  
   return onTouchEvent(ev);
}

最后,如果要在activity里面的onTouchEvent做事件处理的话,需要对dispatchTouchEvent进行重写处理。

二、分析ViewGroup 以及子 View之间的事件传递

1.ViewGroup.dispatchTouchEvent 方法中ACTION_DOWN 事件分发的一个简单流程图:

01.png

2.注意这几个方法调用关系:

其中ViewGroup.dispatchTouchEvent最为重要,事件传递核心实现就是在这里完成的

3.然后看ViewGroup.dispatchTouchEvent源码时候,注意几个重要的地方:

下面开始分析ViewGroup.dispatchTouchEvent方法的实现,可以对照源码自己看。主要讲解ACTION_DOWN事件传递过程,后面ACTION_MOVE,ACTION_UP等事件,依据ACTION_DOWN事件,自己可以走一遍。

4.然后分析标注的第1处代码分析,onInterceptTouchEvent调用

// Check for interception.
final boolean intercepted;
开始时候ACTION_DOWN事件进入 ||  mFirstTouchTarget !=null的情
况,等把ACTION_DOWN事件分析完后,后续的ACTION_MOVE,ACTION_UP再分析
if (actionMasked == MotionEvent.ACTION_DOWN
        || mFirstTouchTarget != null) {   
    final boolean disallowIntercept = (mGroupFlags & 
FLAG_DISALLOW_INTERCEPT) != 0;  //拦截的标志,默认都是false
    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;
}

这里就很简单了,ACTION_DOWN事件时,调用onInterceptTouchEvent 来判断是否被拦截了。注意这里出现的 intercepted 变量后面会用到。

5.第3处代码分析,ACTION_DOWN被子view拦截后的处理

ACTION_DOWN是所有touchEvent事件的起点, 所以在ACTION_DOWN事件传来的时候,对子view里面各个dispatchTouchEvent 和onTouchEvent进行判断 是否需要消耗事件,如果要消耗就返回true,并且为其在mFirstTouchTarget 所在的链式结构上保存,其有后序的事件需要传递过去,就只需要处理这个上面保存的view。

TouchTarget newTouchTarget = null;
boolean alreadyDispatchedToNewTouchTarget = false;
if (!canceled && !intercepted) {    //没有被拦截和事件没有被取消就进去
     
    if (actionMasked == MotionEvent.ACTION_DOWN
            || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
            || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
     
        final int childrenCount = mChildrenCount;
        if (newTouchTarget == null && childrenCount != 0) {
            final float x = ev.getX(actionIndex);
            final float y = ev.getY(actionIndex);
            final View[] children = mChildren;   //view个数
//for循环来遍历当前View下面的子view, 找到哪个view是在点击的范围内
            for (int i = childrenCount - 1; i >= 0; i--) {
                final int childIndex = customOrder
                        ? getChildDrawingOrder(childrenCount, i) : i;
                final View child = (preorderedList == null)
                        ? children[childIndex] : preorderedList.get(childIndex);

                if (childWithAccessibilityFocus != null) {
                    if (childWithAccessibilityFocus != child) {
                        continue;
                    }
                    childWithAccessibilityFocus = null;
                    i = childrenCount - 1;
                }
                
               //不在该view点击范围或者view是在做一些移动的操作及动画,就跳过继续找
                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);

                该方法里面会调用child.dispatchTouchEvent - >child.onTouchEvent ,依次返回结果,返回true表示子view拦截了
                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();


                   //addTouchTarget 方法里面创建target保存对应的view,然后赋值给mFirstTouchTarget, 后续事件传递过来,只需要处理这里里面保存的view
                  注意 这里是在MotionEvent.ACTION_DOWN 事件情况创建,其它事件不会进来这里。
             
                   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();
        }
    }
}

ViewGroup的dispatchTransformedTouchEvent()方法的代码片段:

private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
        View child, int desiredPointerIdBits) {
     ......
    final boolean handled;
    // Perform any necessary transformations and dispatch.
    if (child == null) {
       进入该方法可以看到,交给自己的onTouchEvent处理了
        handled = super.dispatchTouchEvent(transformedEvent);
    } else {
        ......
         
        //这里调用dispatchTouchEvent,  如果child是viewGroup,
       那么又回到了上面,如果是view会看到,最终交给自己的onTouchEvent处理
        handled = child.dispatchTouchEvent(transformedEvent);
    }

    // Done.
    transformedEvent.recycle();
    return handled;
}

View内部的 dispatchTouchEvent方法:

public boolean dispatchTouchEvent(MotionEvent event) {
    // If the event should be handled by accessibility focus first.
   
     ....代码省略
     
    if (onFilterTouchEventForSecurity(event)) {
        if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
            result = true;
        }
        //noinspection SimplifiableIfStatement
        ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnTouchListener != null
                && (mViewFlags & ENABLED_MASK) == ENABLED
                && li.mOnTouchListener.onTouch(this, event)) {
            result = true;
        }

        if (!result && onTouchEvent(event)) {     这里调用自己的onTouchEvent了。
            result = true;
        }
    }

    ....代码省略
    return result;
}

ViewGroup:addTouchTarget的代码:

给mFirstTouchTarget 进行赋值:

private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
 //创建target , 保存对应的child
    final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);

    target.next = mFirstTouchTarget;  //这里形成了链式结构

    mFirstTouchTarget = target;  //mFirstTouchTarget始终指向链头

    return target;
}

结果:
首先mFirstTouchTarget是在上面提到的 addTouchTarget()方法里面被赋值的, 而且该方法是在ACTION_DOWN事件 和 dispatchTransformedTouchEvent返回true的时候才会执行,即ACTION_DOWN事件被子view拦截了,从而依次形成一个链式结构,保存拦截了ACTION_DOWN事件的view。注意去看addTouchTarget方法,很关键: mFirstTouchTarget ->next

6. 第2处代码关键代码以及后续的ACTION_MOVE,ACTION_UP等时事件传递的地方:

看到没这里mFirstTouchTarget开始使用了, 然后会出现3种情况:

这里些代码都是在ViewGroup.dispatchTouchEvent 方法里面顺序调用下来的:

// Dispatch to touch targets.
if (mFirstTouchTarget == null) {   //没有子view需要消耗事件,自己处理
    // No touch targets so treat this as an ordinary view.
    //这里child为Null,所有就会调用自己super.dispatchTouchEvent 
即View.dispatchTouchEvent(),然后会调用自己的onTouchEvent()
    handled = dispatchTransformedTouchEvent(ev, canceled, null,
            TouchTarget.ALL_POINTER_IDS);
} else {    //有子view需要消耗事件
    
    TouchTarget predecessor = null;
    TouchTarget target = mFirstTouchTarget;
    while (target != null) {
      
        1. ACTION_DOWN事件时 alreadyDispatchedToNewTouchTarget = true && target 与newTouchTarget相等,进入if语句,ACTION_DOWN事件在这里就基本结束了。
        final TouchTarget next = target.next;
        if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
            handled = true;
        } else {
            2. 后续ACTION_UP,ACTION_MOVE等事件过来时,进入了else语句。
            

//resetCancelNextUpFlag方法判断target.child是否是被临时移除掉了
            即有可能被GONE了,那么dispatchTransformedTouchEvent()中会发送ACTION_CANCE事件。intercepted即前面的onInterceptTouchEvent()方法返回的,表示是否进行拦截
            
            final boolean cancelChild = resetCancelNextUpFlag(target.child)
                    || intercepted;     
     
            //第3处代码
            //这里就是处理后续子view需要处理的ACTION_MOVE、ACTION_UP等事件处理,上面的cancelChild和 target.child 参数决定是否传递给子view,还是自己处理,具体进入该方法查看
            if (dispatchTransformedTouchEvent(ev, cancelChild,
                    target.child, target.pointerIdBits)) {
                handled = true;
            }
            if (cancelChild) {               
               if (predecessor == null) {
                 //将这里赋值为null  ,下次ACTION_MOVE过来就直接走
                 mFirstTouchTarget  == null的逻辑了
                    mFirstTouchTarget = next; 
                } else {
                    predecessor.next = next;
                }
                target.recycle();
                target = next;
                continue;
            }
        }
        predecessor = target;
        target = next;
    }
}

}

return handled;

为什么在1处只会有ACTION_DOWN事件会执行,代码2处执行的是其它事件了?

首先 mFirstTouchTarget !=null,前面分析了什么情况下mFirstTouchTarget不为空了

在 ACTION_DOWN事件时:
  1. 上面newTouchTarget=addTouchTarget()处代码,可知此时target与newTouchTarget是相等的,进入if语句

  2. 然后执行while循环最下面的代码:

predecessor = target
target = next;

可以得到target = null了,while循环就结束了,MOTION_DOWN事件处理结束

  1. 为什么此时target = next会为null了?
      next为mFirstTouchTarget.next, mFirstTouchTarget指向ViewGroup中第一个拦截ACTION_DOWN事件的view,而ViewGroup中有且仅有一个子view会被点击,因此mFirstTouchTarget.next就为null了。
在ACTION_MOVE和ACTION_UP等事件时:

假如有个这样层次结构 ViewGroup1 - > ViewGroup2 - >View3 , View3需要有点击事件,那么就需要ACTION_DOWN和ACTION_UP。所以就需要先有ACTION_DOWN, 然后就有mFirstTouchTarget !=null,然后处理ACTION_UP事件传递下去的情况:

1.首先调用 ViewGroup1.dispatchTouchEvent() 方法,如下的代码片段
    target = mFirstTouchTarget;   //此时mFirstTouchTarget.child = ViewGroup2
    while(target !=null){
          dispatchTransformedTouchEvent(target.child) 代码1处
          target = mFirstTouchTarget.next;  //这里target为null
    }
            
 2. 在代码1处
    这里child为ViewGroup2,又会执行ViewGroup2.dispatchTouchEvent() 此时
ViewGroup2.mFirstTouchTarget.child = View3, 

3. 在ViewGroup2.dispatchTouchEvent()方法里面的代码1处,
child为View3, dispatchTransformedTouchEvent里面调用child.dispatchTouchEvent即View.dispatchTouchEvent,然后会调用onTouchEvent方法,此时ACTION_UP事件就传递到View3了。

        

7.第4处代码简单介绍:

你就直接找到View.dispatchTouchEvent里面的onTouchEvent方法就可以了,看里面方法的返回值处理,这是需要回传给前面调用的方法的。

8. 其它事件处理情况:

如果是ACTION_MOVE事件过程,根据ACTION_DOWN的流程跟着走一遍,主要就是看mFirstTouchTarget 变量在ACTION_DOWN事件的时候发生的变化。

9.然后再简单分析下ScrollView里面有一个Button的情况:

  1. 在onInterceptTouchEvent 里面,ACTION_DOWN没有拦截,ACTION_MOVE事件拦截了,ACTION_UP没有拦截

  2. 所以BUTTON就能接收到ACTION_DOWN 和 ACTION_UP事件,可以做点击事件处理

  3. ACTION_MOVE被拦截了,Scrollview可以进行滑动处理,但是后面ACTION_UP还是会被BUTTON接收,为什么没有点击事件了?

因为在View的onTouchEvent()方法里面在ACTION_MOVE的情况下,执行了如下代码片段:

case MotionEvent.ACTION_MOVE:
      drawableHotspotChanged(x, y);

          // Be lenient about moving outside of buttons
         f (!pointInView(x, y, mTouchSlop)) {
               // Outside button
               removeTapCallback();    //移除了点击事件消息
                if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
                       // Remove any future long press/tap checks
                        removeLongPressCallback();   //移除了长按事件的消息

                        setPressed(false);
                 }
         }
      break;
上一篇下一篇

猜你喜欢

热点阅读