Android笔记:触摸事件的分析与总结----TouchEvent处理机制,事件分发处理机制

Android中的事件类型分为按键事件和屏幕触摸事件。TouchEvent是屏幕触摸事件的基础事件,要深入了解屏幕触摸事件的处理机制,就必须掌握TouchEvent在整个触摸事件中的转移和处理过程。此处将对TouchEvent处理机制的学习做个小小的总结和备记。

当屏幕中包含一个ViewGroup,而这个ViewGroup又包含一个子view,这个时候android系统如何处理Touch事件呢?到底是 ViewGroup来处理Touch事件,还是子view来处理Touch事件呢?

这问题涉及到与每个View或者ViewGroup的子类都具有的三个和TouchEvent处理密切相关的方法:

1)dispatchTouchEvent(MotionEvent ev) 这个方法用来分发TouchEvent

2)onInterceptTouchEvent(MotionEvent ev) 这个方法用来拦截TouchEvent

3)onTouchEvent(MotionEvent ev) 这个方法用来处理TouchEvent

其中view类和Activity中都有dispatchTouchEvent()和onTouchEvent()两个方法。ViewGroup继承自View,而且还新添了一个onInterceptTouchEvent()方法。

这三个方法的返回值都是boolean值,对于返回结果,如果return true,那么表示该方法消费了此次事件,如果return false,那么表示该方法并未处理完全,该事件仍然需要以某种方式传递下去继续等待处理。

一、dispatchTouchEvent 事件分发
dispatchTouchEvent(MotionEventev) 这个方法用来分发TouchEvent,默认返回false。

先看下Activity中的注释和方法:
   /**
 * Called to process touch screen events.  You can override this to
 * intercept all touch screen events before they are dispatched to the
 * window.  Be sure to call this implementation for touch screen events
 * that should be handled normally.
 * 
 * @param ev The touch screen event.
 * 
 * @return boolean Return true if this event was consumed.
 */
public boolean dispatchTouchEvent(MotionEvent ev)
{
    if (ev.getAction() == MotionEvent.ACTION_DOWN)
    {
        onUserInteraction();
    }
    if (getWindow().superDispatchTouchEvent(ev))
    {
        return true;
    }
    return onTouchEvent(ev);
}

二:onInterceptTouchEvent 事件拦截
onInterceptTouchEvent()默认返回了false,注释的大意为重写该方法可以实现对触屏事件的拦截,使用该方法需要特别注意的是,该方法与View类的onTouchEvent(MotionEvent)或者View.onTouchEvent(MotionEvent)方法具有复杂的关联机制。结合onTouchEvent(),总结下onInterceptTouchEvent()大致的规则为:

  1. down事件首先会传递到onInterceptTouchEvent()方法。

  2. 如果该ViewGroup的onInterceptTouchEvent()在接收到down事件处理完成之后return false,那么后续的move, up等事件将继续会先传递给该ViewGroup,之后才和down事件一样传递给最终的目标View的onTouchEvent()处理。

  3. 如果该ViewGroup的onInterceptTouchEvent()在接收到down事件处理完成之后return true,那么后续的move, up等事件将不再传递给onInterceptTouchEvent(),而是和down事件一样传递给该ViewGroup的onTouchEvent()处理,注意,目标view将接收不到任何事件。

  4. 如果最终需要处理事件的view的onTouchEvent()返回了false,那么该事件将被传递至其上一层次的view的onTouchEvent()处理。

  5. 如果最终需要处理事件的view 的onTouchEvent()返回了true,那么后续事件将可以继续传递给该view的onTouchEvent()处理。

三、onTouchEvent 事件响应处理

onTouch()方法与View类中的onTouchEvent()方法的区别

onTouch()是View.setOnTouchListener后调用的方法,如果一个View对象setOnTouchListener后,同时又重写了View类自身的onTouchEvent()方法,那么当屏幕触摸时会先调用哪个方法呢?

每当事件产生时,都要先经由dispatchTouchEvent方法来分发。看下View类中的dispatchTouchEvent()源码,如下:

/**
 * Pass the touch screen motion event down to the target view, or this view
 * if it is the target.
 * 
 * @param event
 *            The motion event to be dispatched.
 * @return True if the event was handled by the view, false otherwise.
 */
public boolean dispatchTouchEvent(MotionEvent event)
{
    // 用于测试目,直接忽略
    if (mInputEventConsistencyVerifier != null)
    {
        mInputEventConsistencyVerifier.onTouchEvent(event, 0);
    }
     
    // 上面代码的第2个标注。 未被其他窗口遮盖
    if (onFilterTouchEventForSecurity(event))
    {
        // noinspection SimplifiableIfStatement
        if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && mOnTouchListener.onTouch(this, event))
        {
            // 如果有监听器,执行监听器的,不在执行当前视图的onTouchEvent方法
            return true;
        }
        // 执行当前视图的onTouchEvent方法
        if (onTouchEvent(event))
        {
            return true;
        }
    }
     
    // 用于测试目,直接忽略
    if (mInputEventConsistencyVerifier != null)
    {
        mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
    }
    return false;
}

dispatchTouchEvent()方法里先是判断了是否有监听器,然后才会去判断onTouchEvent(event)。因此会先执行onTouch()方法,如果监听器的onTouch()返回false,则继续执行View的onTouchEvent()。

分析总结

1.三个主要相关的方法的默认值

所有dispatchTouchEvent方法的默认值都是false。

ViewGroup里的onInterceptTouchEvent默认值是false这样才能把事件传给View里的onTouchEvent.

Activity和ViewGroup里的onTouchEvent默认值都是false。

View里的onTouchEvent返回默认值是true.这样才能执行多次touch事件。

2.TouchEvent的处理流程

当TouchEvent发生时,首先Activity将TouchEvent传递给最顶层的View, TouchEvent最先到达最顶层 view 的 dispatchTouchEvent ,然后由 dispatchTouchEvent 方法进行分发,如果dispatchTouchEvent返回true ,则表示该触摸事件已经被消费了,如果dispatchTouchEvent返回 false ,则交给这个 view 的 interceptTouchEvent 方法来决定是否要拦截这个事件,如果 interceptTouchEvent 返回 true ,也就是拦截掉了,则交给它的 onTouchEvent 来处理,如果 interceptTouchEvent 返回 false ,那么就传递给子 view ,由子 view 的 dispatchTouchEvent 再来开始这个事件的分发。如果事件传递到某一层的子 view 的 onTouchEvent 上了,这个方法返回了 false ,那么这个事件会从这个 view 往上传递,都是 onTouchEvent 来接收。而如果传递到最上面的 onTouchEvent 也返回 false 的话,这个事件就会“ 消失”,而且接收不到下一次事件。

3.TouchEvent的处理流程图

自己制作了个TouchEvent处理的流程图,方便理清TouchEvent事件在各种UI对象以及对应方法中的处理机制。将流程图与上面的运行日志结合分析,发现对TouchEvent处理的机制清晰了很多。若有错误之处,欢迎指教。

在这里插入图片描述

View源码分析
Android中ImageView、textView、Button等继承于View但没有重写的dispatchTouchEvent方法,所以都用的View的该方法进行事件分发。

看View重要函数部分源码:

public boolean dispatchTouchEvent(MotionEvent event) { 
      //返回true,表示该View内部消化掉了所有事件。返回false,表示View内部只处理了ACTION_DOWN事件,事件继续传递,向上级View(ViewGroup)传递。 
    if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && 
            mOnTouchListener.onTouch(this, event)) {   
             //此处的onTouch方式就是回调的我们注册OnTouchListener时重写的onTouch()方法 
         return true; 
    }    return onTouchEvent(event); 
}  

ViewGroup源码分析
Android中诸如LinearLayout等的五大布局控件,都是继承自ViewGroup,而ViewGroup本身是继承自View,所以ViewGroup的事件处理机制对这些控件都有效。
部分源码:

public boolean dispatchTouchEvent(MotionEvent ev) {   
       final int action = ev.getAction();   
       final float xf = ev.getX();   
       final float yf = ev.getY();   
       final float scrolledXFloat = xf + mScrollX;   
       final float scrolledYFloat = yf + mScrollY;   
       final Rect frame = mTempRect;   
   
       //这个值默认是false, 然后我们可以通过requestDisallowInterceptTouchEvent(boolean disallowIntercept)方法   
       //来改变disallowIntercept的值   
       boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;   
   
       //这里是ACTION_DOWN的处理逻辑   
       if (action == MotionEvent.ACTION_DOWN) {   
        //清除mMotionTarget, 每次ACTION_DOWN都很设置mMotionTarget为null   
           if (mMotionTarget != null) {   
               mMotionTarget = null;   
           }   
   
           //disallowIntercept默认是false, 就看ViewGroup的onInterceptTouchEvent()方法   
           if (disallowIntercept || !onInterceptTouchEvent(ev)) {  //第一点 
               ev.setAction(MotionEvent.ACTION_DOWN);   
               final int scrolledXInt = (int) scrolledXFloat;   
               final int scrolledYInt = (int) scrolledYFloat;   
               final View[] children = mChildren;   
               final int count = mChildrenCount;   
               //遍历其子View   
               for (int i = count - 1; i >= 0; i--) {  //第二点 
                   final View child = children[i];   
                      
                   //如果该子View是VISIBLE或者该子View正在执行动画, 表示该View才   
                   //可以接受到Touch事件   
                   if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE   
                           || child.getAnimation() != null) {   
                    //获取子View的位置范围   
                       child.getHitRect(frame);   
                          
                       //如Touch到屏幕上的点在该子View上面   
                       if (frame.contains(scrolledXInt, scrolledYInt)) {   
                           // offset the event to the view's coordinate system   
                           final float xc = scrolledXFloat - child.mLeft;   
                           final float yc = scrolledYFloat - child.mTop;   
                           ev.setLocation(xc, yc);   
                           child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;   
                              
                           //调用该子View的dispatchTouchEvent()方法   
                           if (child.dispatchTouchEvent(ev))  {   
                               // 如果child.dispatchTouchEvent(ev)返回true表示   
                            //该事件被消费了,设置mMotionTarget为该子View   
                               mMotionTarget = child;   
                               //直接返回true   
                               return true;   
                           }   
                           // The event didn't get handled, try the next view.   
                           // Don't reset the event's location, it's not   
                           // necessary here.   
                       }   
                   }   
               }   
           }   
       }   
   
       //判断是否为ACTION_UP或者ACTION_CANCEL   
       boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) ||   
               (action == MotionEvent.ACTION_CANCEL);   
   
       if (isUpOrCancel) {   
        //如果是ACTION_UP或者ACTION_CANCEL, 将disallowIntercept设置为默认的false   
        //假如我们调用了requestDisallowInterceptTouchEvent()方法来设置disallowIntercept为true   
        //当我们抬起手指或者取消Touch事件的时候要将disallowIntercept重置为false   
        //所以说上面的disallowIntercept默认在我们每次ACTION_DOWN的时候都是false   
           mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;   
       }   
   
       // The event wasn't an ACTION_DOWN, dispatch it to our target if   
       // we have one.   
       final View target = mMotionTarget;   
       //mMotionTarget为null意味着没有找到消费Touch事件的View, 所以我们需要调用ViewGroup父类的   
       //dispatchTouchEvent()方法,也就是View的dispatchTouchEvent()方法   
       if (target == null) {   
           // We don't have a target, this means we're handling the   
           // event as a regular view.   
           ev.setLocation(xf, yf);   
           if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {   
               ev.setAction(MotionEvent.ACTION_CANCEL);   
               mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;   
           }   
           return super.dispatchTouchEvent(ev);   
       }   
   
       //这个if里面的代码ACTION_DOWN不会执行,只有ACTION_MOVE   
       //ACTION_UP才会走到这里, 假如在ACTION_MOVE或者ACTION_UP拦截的   
       //Touch事件, 将ACTION_CANCEL派发给target,然后直接返回true   
       //表示消费了此Touch事件   
       if (!disallowIntercept && onInterceptTouchEvent(ev)) {   
           final float xc = scrolledXFloat - (float) target.mLeft;   
           final float yc = scrolledYFloat - (float) target.mTop;   
           mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;   
           ev.setAction(MotionEvent.ACTION_CANCEL);   
           ev.setLocation(xc, yc);   
              
           if (!target.dispatchTouchEvent(ev)) {   
           }   
           // clear the target   
           mMotionTarget = null;   
           // Don't dispatch this event to our own view, because we already   
           // saw it when intercepting; we just want to give the following   
           // event to the normal onTouchEvent().   
           return true;   
       }   
   
       if (isUpOrCancel) {   
           mMotionTarget = null;   
       }   
   
       // finally offset the event to the target's coordinate system and   
       // dispatch the event.   
       final float xc = scrolledXFloat - (float) target.mLeft;   
       final float yc = scrolledYFloat - (float) target.mTop;   
       ev.setLocation(xc, yc);   
   
       if ((target.mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {   
           ev.setAction(MotionEvent.ACTION_CANCEL);   
           target.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;   
           mMotionTarget = null;   
       }   
   
       //如果没有拦截ACTION_MOVE, ACTION_DOWN的话,直接将Touch事件派发给target   
       return target.dispatchTouchEvent(ev);   
   }  
发布了36 篇原创文章 · 获赞 11 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/qq_34895720/article/details/94847744