zoukankan      html  css  js  c++  java
  • Android Touch事件传递机制详解 上


           最近总是遇到关于Android Touch事件的问题,如:滑动冲突的问题,以前也花时间学习过Android Touch事件的传递机制,可以每次用起来的时候总是忘记了,索性自己总结一下写篇文章避免以后忘记了,其实网上关于Touch事件的传递的文章真的很多,但是很少有系统性的,都是写了一个简单的demo运行了一下,对于我们了解Android Touch事件基本上没有任何帮助。

           今天我打算从源码的角度来分析一下Touch事件的传递机制。在了解Touch事件之前,最好了解下Android中窗口的创建过程,这样对于Android窗口的整体结构和事件的传递过程会了解更深。

           我就把事件的始点定在PhoneWindow中的DecorView吧,至于是谁把事件传递给DecorView的我们先不用去关心它。(如果想深入研究,请阅读我的另外一篇文章Android中按键事件传递机制)我们只需要知道它的上家是通过dispatchTouchEvent方法将事件分发给DecorView就行了,我进入到该方法瞧瞧究竟。

    在阅读之前最好阅读Android窗口创建过程

    1. @Override  
    2.         public boolean dispatchTouchEvent(MotionEvent ev)   
    3.     {  
    4.         //该Callback就是该DecorView附属的Activity,可以看我的另外一篇文章《Android中窗口的创建过程》  
    5.             final Callback cb = getCallback();  
    6.         //如果cb!=null && mFeatureId<0 就执行Activity中的dispatchTouchEvent方法,对于应用程序窗口 <span >         </span>    //这两个条件一般是满足的  
    7.             return cb != null && mFeatureId < 0 ? cb.dispatchTouchEvent(ev) : super  
    8.                     .dispatchTouchEvent(ev);  
    9.         }  

    在DecorView中事件通过dispatchTouchEvent方法被分发到了Activity中,相信Activity对于每个Android开发者都不会陌生吧,那我们就进入Activity的dispatchTouchEvent方法中。

    1. public boolean dispatchTouchEvent(MotionEvent ev) {  
    2.        if (ev.getAction() == MotionEvent.ACTION_DOWN) {  
    3.            onUserInteraction();  
    4.        }  
    5. //getWindow返回什么?如果阅读过我的《Android中窗口创建过程》的都知道就是PhoneWindow,如果PhoneWindow中的superDispatchTouchEvent方法返回了true,  
    6. //那么该Touch事件就被PhoneWindow给消费掉了,不会再继续传递,如果返回false,那么就会执行Activity的onTouchEvent方法  
    7.        if (getWindow().superDispatchTouchEvent(ev)) {  
    8.            return true;  
    9.        }  
    10.        return onTouchEvent(ev);  
    11.    }  

    进入PhoneWindow中的superDispatchTouchEvent方法:

    1. @Override  
    2.     public boolean superDispatchTouchEvent(MotionEvent event) {  
    3.         //mDecor是一个DecorView类型变量  
    4.         return mDecor.superDispatchTouchEvent(event);  
    5.     }  


    进入DecorView中的superDispatchTouchEvent方法:

    1. public boolean superDispatchTouchEvent(MotionEvent event) {  
    2.             //直接调用父类的dispatchTouchEvent方法  
    3.             return super.dispatchTouchEvent(event);  
    4.         }  


    走到这里我们先暂停一下,会看一下DecorView类的dispatchTouchEvent方法,如果callBack不为空,那么调用CallBack的dispatchTouchEvent方法,否则调用super.dispatchTouchEvent方法,但是在CallBack不为空的条件下最中也是调用到了super.dispatchTouchEvent方法,那么它的super是哪个那,我们继续往下看:
    通过源码我们可以看到DecorView是继承自FrameLayout。所以事件最终是传递到了FrameLayout的dispatchTouchEvent中,FrameLayout中的此方法是继承自ViewGroup的,我们直接到ViewGroup中查看此方法吧:

    1. @Override  
    2.     public boolean dispatchTouchEvent(MotionEvent ev) {  
    3.         final int action = ev.getAction();  
    4.         final float xf = ev.getX();  
    5.         final float yf = ev.getY();  
    6.         final float scrolledXFloat = xf + mScrollX;  
    7.         final float scrolledYFloat = yf + mScrollY;  
    8.         final Rect frame = mTempRect;  
    9.         //可以通过requestDisallowInterceptTouchEvent方法来设置该变量的值,通常是false  
    10.         boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;  
    11.   
    12.         if (action == MotionEvent.ACTION_DOWN) {  
    13.             if (mMotionTarget != null) {  
    14.                 // this is weird, we got a pen down, but we thought it was  
    15.                 // already down!  
    16.                 // XXX: We should probably send an ACTION_UP to the current  
    17.                 // target.  
    18.                 mMotionTarget = null;  
    19.             }  
    20.             // If we're disallowing intercept or if we're allowing and we didn't  
    21.             // intercept  
    22.             //onInterceptTouchEvent在默认情况下是返回false的,所以这里通常是可以进去的  
    23.             if (disallowIntercept || !onInterceptTouchEvent(ev)) {  
    24.                 // reset this event's action (just to protect ourselves)  
    25.                 ev.setAction(MotionEvent.ACTION_DOWN);  
    26.                 // We know we want to dispatch the event down, find a child  
    27.                 // who can handle it, start with the front-most child.  
    28.                 final int scrolledXInt = (int) scrolledXFloat;  
    29.                 final int scrolledYInt = (int) scrolledYFloat;  
    30.                 final View[] children = mChildren;  
    31.                 final int count = mChildrenCount;  
    32.                 //遍历ViewGroup的孩子,如果触摸点在某一个子View中,则调用在子View的dispatchTouchEvent  
    33.                 for (int i = count - 1; i >= 0; i--) {  
    34.                     final View child = children[i];  
    35.                     if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE  
    36.                             || child.getAnimation() != null) {  
    37.                         child.getHitRect(frame);  
    38.                         if (frame.contains(scrolledXInt, scrolledYInt)) {  
    39.                             // offset the event to the view's coordinate system  
    40.                             final float xc = scrolledXFloat - child.mLeft;  
    41.                             final float yc = scrolledYFloat - child.mTop;  
    42.                             ev.setLocation(xc, yc);  
    43.                             child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;  
    44.                 //调用了某一个子View 的dispatchTouchEvent ,如果这个子View 的dispatchTouchEvent返回true,那么意味着这个事件  
    45.                 //已经被这个子View消费了,不会继续传递  
    46.                             if (child.dispatchTouchEvent(ev))  {  
    47.                                 // Event handled, we have a target now.  
    48.                                 mMotionTarget = child;  
    49.                                 return true;  
    50.                             }  
    51.                             // The event didn't get handled, try the next view.  
    52.                             // Don't reset the event's location, it's not  
    53.                             // necessary here.  
    54.                         }  
    55.                     }  
    56.                 }  
    57.             }  
    58.         }  
    59.   
    60.         boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) ||  
    61.                 (action == MotionEvent.ACTION_CANCEL);  
    62.   
    63.         if (isUpOrCancel) {  
    64.             // Note, we've already copied the previous state to our local  
    65.             // variable, so this takes effect on the next event  
    66.             mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;  
    67.         }  
    68.   
    69.         // The event wasn't an ACTION_DOWN, dispatch it to our target if  
    70.         // we have one.  
    71.         final View target = mMotionTarget;  
    72.         //对于一个Action_down事件,如果走到了这里,说明所有的子View 都没有消费掉这个事件,那么它就调用父类的  
    73.         //的dispatchTouchEvnet方法,ViewGroup的父类就是View  
    74.         if (target == null) {  
    75.             // We don't have a target, this means we're handling the  
    76.             // event as a regular view.  
    77.             ev.setLocation(xf, yf);  
    78.             if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {  
    79.                 ev.setAction(MotionEvent.ACTION_CANCEL);  
    80.                 mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;  
    81.             }  
    82.             return super.dispatchTouchEvent(ev);  
    83.         }  
    84.   
    85.         // if have a target, see if we're allowed to and want to intercept its  
    86.         // events  
    87.         if (!disallowIntercept && onInterceptTouchEvent(ev)) {  
    88.             final float xc = scrolledXFloat - (float) target.mLeft;  
    89.             final float yc = scrolledYFloat - (float) target.mTop;  
    90.             mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;  
    91.             ev.setAction(MotionEvent.ACTION_CANCEL);  
    92.             ev.setLocation(xc, yc);  
    93.             if (!target.dispatchTouchEvent(ev)) {  
    94.                 // target didn't handle ACTION_CANCEL. not much we can do  
    95.                 // but they should have.  
    96.             }  
    97.             // clear the target  
    98.             mMotionTarget = null;  
    99.             // Don't dispatch this event to our own view, because we already  
    100.             // saw it when intercepting; we just want to give the following  
    101.             // event to the normal onTouchEvent().  
    102.             return true;  
    103.         }  
    104.   
    105.         if (isUpOrCancel) {  
    106.             mMotionTarget = null;  
    107.         }  
    108.   
    109.         // finally offset the event to the target's coordinate system and  
    110.         // dispatch the event.  
    111.         final float xc = scrolledXFloat - (float) target.mLeft;  
    112.         final float yc = scrolledYFloat - (float) target.mTop;  
    113.         ev.setLocation(xc, yc);  
    114.   
    115.         if ((target.mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {  
    116.             ev.setAction(MotionEvent.ACTION_CANCEL);  
    117.             target.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;  
    118.             mMotionTarget = null;  
    119.         }  
    120.   
    121.         return target.dispatchTouchEvent(ev);  
    122.     }  


    刚才在看ViewGroup的dispatchTouchEvent方法时,我们看到了一个方法onInterceptTouchEvent,这个方法是干什么的呢,我们先看看他都干了什么吧

    1. public boolean onInterceptTouchEvent(MotionEvent ev) {  
    2.         return false;  
    3.     }  


    发现里面就是返回了一个false, 通过方法名字我们就可以知道该方法的作用,是否阻止TouchEvent的传递,默认是false 也就是不会阻止。

    现在总结一下ViewGroup的dispatchTouchEvnet的逻辑 ,毕竟这个方法有些复杂:
    1、如果disallowIntercept|| !onInterceptTouchEvent(),那么事件才可以继续传递下去,否则直接调用该ViewGroup的父类的dispatchTouchEvent,也就是View的dispatchTouchEvent.
    2、依次遍历ViewGroup的所有子View,将事件传递个子View,如果某一个子View处理了该事件,并且返回true,那么事件结束,停止传递
    3、如果所有的子View没有消费掉这个事件,那么就调用View的dispatchTouchEvent

    对于任何一款Android应用,展现给用户最上面的通常就是一个View,如Button,ImageView等等,也就是说一些触摸事件最终都是传递给了这个控件,如果控件消费了这些事件,那么就停止传递了,如果没有消费,那么就交给控件所属ViewGroup的onTouchEvnet处理,我们就看看View的dispatchTouchEvent方法吧

    1. public boolean dispatchTouchEvent(MotionEvent event) {  
    2.         if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&  
    3.                 mOnTouchListener.onTouch(this, event)) {  
    4.             return true;  
    5.         }  
    6.         return onTouchEvent(event);  
    7.     }  


    View的这个方法非常简单,首先判断mTouchListener是否为空,并且这个View是否Eneable,如果都满足,那么首先调用mOnTouchListener.onTouch方法,如果onTouch方法返回true,那么就是说这个View消费了该事件,直接返回true,如果onTouch返回false,那么就会调用onTouchEvnet方法,这个mOnTouchListener是什么?

    1. public void setOnTouchListener(OnTouchListener l) {  
    2.        mOnTouchListener = l;  
    3.    }  


    看了这个就明白了吧,就是我们通过setOnTouchListener赋值的,另外我们还需要注意一点就是这个onTouch是在onTouchEvent方法之前执行的哦。
    最后我们就看看这个View的onTouchEvnet吧

    1. public boolean onTouchEvent(MotionEvent event) {  
    2.         final int viewFlags = mViewFlags;  
    3.         //(A)  
    4.         if ((viewFlags & ENABLED_MASK) == DISABLED) {  
    5.             // A disabled view that is clickable still consumes the touch  
    6.             // events, it just doesn't respond to them.  
    7.             return (((viewFlags & CLICKABLE) == CLICKABLE ||  
    8.                     (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));  
    9.         }  
    10.   
    11.         if (mTouchDelegate != null) {  
    12.             if (mTouchDelegate.onTouchEvent(event)) {  
    13.                 return true;  
    14.             }  
    15.         }  
    16.         //(B)  
    17.         if (((viewFlags & CLICKABLE) == CLICKABLE ||  
    18.                 (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {  
    19.             switch (event.getAction()) {  
    20.                 case MotionEvent.ACTION_UP:  
    21.                     boolean prepressed = (mPrivateFlags & PREPRESSED) != 0;  
    22.                     if ((mPrivateFlags & PRESSED) != 0 || prepressed) {  
    23.                         // take focus if we don't have it already and we should in  
    24.                         // touch mode.  
    25.                         boolean focusTaken = false;  
    26.                         if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {  
    27.                             focusTaken = requestFocus();  
    28.                         }  
    29.   
    30.                         if (!mHasPerformedLongPress) {  
    31.                             // This is a tap, so remove the longpress check  
    32.                             removeLongPressCallback();  
    33.   
    34.                             // Only perform take click actions if we were in the pressed state  
    35.                             if (!focusTaken) {  
    36.                                 // Use a Runnable and post this rather than calling  
    37.                                 // performClick directly. This lets other visual state  
    38.                                 // of the view update before click actions start.  
    39.                                 if (mPerformClick == null) {  
    40.                                     mPerformClick = new PerformClick();  
    41.                                 }  
    42.                                 //(C)  
    43.                                 if (!post(mPerformClick)) {  
    44.                                     performClick();  
    45.                                 }  
    46.                             }  
    47.                         }  
    48.   
    49.                         if (mUnsetPressedState == null) {  
    50.                             mUnsetPressedState = new UnsetPressedState();  
    51.                         }  
    52.   
    53.                         if (prepressed) {  
    54.                             mPrivateFlags |= PRESSED;  
    55.                             refreshDrawableState();  
    56.                             postDelayed(mUnsetPressedState,  
    57.                                     ViewConfiguration.getPressedStateDuration());  
    58.                         } else if (!post(mUnsetPressedState)) {  
    59.                             // If the post failed, unpress right now  
    60.                             mUnsetPressedState.run();  
    61.                         }  
    62.                         removeTapCallback();  
    63.                     }  
    64.                     break;  
    65.   
    66.                 case MotionEvent.ACTION_DOWN:  
    67.                     if (mPendingCheckForTap == null) {  
    68.                         mPendingCheckForTap = new CheckForTap();  
    69.                     }  
    70.                     mPrivateFlags |= PREPRESSED;  
    71.                     mHasPerformedLongPress = false;  
    72.                     postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());  
    73.                     break;  
    74.   
    75.                 case MotionEvent.ACTION_CANCEL:  
    76.                     mPrivateFlags &= ~PRESSED;  
    77.                     refreshDrawableState();  
    78.                     removeTapCallback();  
    79.                     break;  
    80.   
    81.                 case MotionEvent.ACTION_MOVE:  
    82.                     final int x = (int) event.getX();  
    83.                     final int y = (int) event.getY();  
    84.   
    85.                     // Be lenient about moving outside of buttons  
    86.                     int slop = mTouchSlop;  
    87.                     if ((x < 0 - slop) || (x >= getWidth() + slop) ||  
    88.                             (y < 0 - slop) || (y >= getHeight() + slop)) {  
    89.                         // Outside button  
    90.                         removeTapCallback();  
    91.                         if ((mPrivateFlags & PRESSED) != 0) {  
    92.                             // Remove any future long press/tap checks  
    93.                             removeLongPressCallback();  
    94.   
    95.                             // Need to switch from pressed to not pressed  
    96.                             mPrivateFlags &= ~PRESSED;  
    97.                             refreshDrawableState();  
    98.                         }  
    99.                     }  
    100.                     break;  
    101.             }  
    102.             //(D)  
    103.             return true;  
    104.         }  
    105.   
    106.         return false;  
    107.     }  


    这个方法也是相当的复杂啊,但是我们没有必要每一行都看,我们只需要挑重点看就Ok了。
    请细看我标了 A B C D的四个地方,在A处,如果该View是Disable的,那么只要该View是clickable或者longclickable的,那么这个事件就被该View消费掉了,返回true
    在看B 和 D,两处,如果该View是clickable或者longclickable的,那么D出总是返回true,也就是说事件一直被消费,至于C处我主要是说明的是View的onClick事件是在ACTION_UP中触发的。

    学习到这里,我又需要总结一下:
    如果我们触摸的一个View是clickable或者longclickable的,那么这个事件肯定会被这个View消费掉(当然前提是你没有改写它所在ViewGroup的onInterceptTouchEvent方法,如果你改写此方法返回true,那么View是无法接收到这个事件的)

    我们现在还要思考一个问题,如果这个View没有消费掉这个事件,这个事件最终抛向何方?
    还记得前面我说过ViewGroup的dispatchTouchEvent方法吗,如果它的所有的子View没有处理掉该事件,那么调用的是父类View的dispatchTouchEvnet方法,从而执行到了该ViewGroup的onTouch和onTouchEvent方法。

    那如果ViewGroup也没有处理该事件呢,这里就要分两种情况啦:
    1、如果这个ViewGroup不是DecorView,也就是说他的父View就是一个普通的ViewGroup(如LinearLayout里面放置一个LinearLayout),那么和上面子View没有处理掉消息有点类似,调用父类的onTouch和onTouchEvent方法
    2、如果这个ViewGroup就是DecorView,那么就调用到了Activity的onTouchEvnet方法(此时没有onTouch方法)。

    今天就先写到这里吧,后面我回用一个简单的Demo和一个简单的滑动冲突问题在深入学习TouchEvnet事件的。如果哪里没有写清楚的 ,欢迎拍砖。。。

  • 相关阅读:
    java如何操作注册表(Preferences类)(在windows的注册表中保存、读取)
    转-正则表达式
    js数字格式化千分位格式
    es6严格模式需要注意的地方
    html5手势原理知识
    js事件总结
    js键盘相关知识总结
    html5 拖放
    js学习日记-隐式转换相关的坑及知识
    移动端各种分辨率匹配
  • 原文地址:https://www.cnblogs.com/android-blogs/p/5741349.html
Copyright © 2011-2022 走看看