Android Touch事件传递机制详解

2021-07-08
0

Android点击事件传递机制详解

在讲正题之前我们讲一段有关任务传递的小故事,抛砖迎玉下:

话说一家软件公司,来一个任务,分派给了开发经理去完成:

开发经理拿到,看了一下,感觉好简单,于是

开发经理:分派给了开发组长

开发组长:分派给了自己组员(程序员)

程序员:分派给了自己带的实习生。

实习生:好苦逼,无法分派,怎么办啊?只能自己干了

但是实习生能不能做好,有两种情况了。

情况一:

实习生:经过一段时间的研究,琢磨,熬夜,奋斗,死敲,皇天不负有心人啊,完成了。

后来又来一个类似的任务,也按着这样传递下去了(开发经理->开发组长->程序员->实习生),又有实习生完成了。

情况二:

实习生:经过一段时间的研究,琢磨,就是毫无头绪,无法完成,只能求教师傅(程序员)了。

程序员:啊,我怎么没留意就给实习生搞了,这任务好难啊,自己研究下,也没有头绪,没办法只能请求组长了。

开发组长:这任务不难啊,怎么我底下的人都不会了,没办法,只能自己搞了,经过,一段时间,完成了,感想,以后要是又有跟这个很类似的任务,我就自己弄了,不给他们弄了。

后来又来一个类似的任务,传递是这样的

开发经理:分派给开发组长

开发组长:啊,又是跟着上一个很类似的任务,我自己弄吧,没过多久也完成了!

PS:以上就是一个任务传递的过程。上级一开始总会想把任务分派给自己的下属去弄,然后任务一步一步去传递下去,最后由一个人去完成他,当然传递下 去,最底下人不会,也会一步一步的回滚,又他上级去完成,如果又有一个类似的任务,那个节点不会的人就不会再传递给他,给他也不会弄。当然实现中,这些任 务的传递也可能被分派人去拦截,比如说,开发组长本打算分派给程序员,突然他不想传递了,就半路拦截了下来。

而在我们Android Touch事件传递机制跟这个很类似,思想差不多,有句话说,设计来源于生活。跟Touch事件有关的处理方法主要由三个:

//分派事件
public boolean dispatchTouchEvent(MotionEvent ev)
//拦截事件 
public boolean onInterceptTouchEvent(MotionEvent ev)
//处理事件
public boolean onTouchEvent(MotionEvent event)

用他们的时候,我们只需要重写一下,即可操作,这三个方法主要在三种类被调用,那三种类呢?

基类 例子类 拥有的方法
继承Activity(activity类) MainActivity(因项目而异)
dispatchTouchEvent,onTouchEvent
继承ViewGroup(View容器) RelativeLayout,FrameLayout,LinearLayout,AbsoluteLayout,ListView,ScrollView…
dispatchTouchEvent,onInterceptTouchEvent,onTouchEvent
继承View(View控件) Button,EditText,TextView,ImageView….
dispatchTouchEvent,onTouchEvent

而这三个方法的含义是什么呢?

事件 含义
dispatchTouchEvent
用来分派事件。
其中调用了onInterceptTouchEvent()和onTouchEvent(),一般不重写该方法
onInterceptTouchEvent
用来拦截事件。
ViewGroup类中的源码实现就是{return false;}表示不拦截该事件,
事件将向下传递(传递给其子View);
若手动重写该方法,使其返回true则表示拦截,事件将终止向下传递,
事件由当前ViewGroup类来处理,就是调用该类的onTouchEvent()方法
onTouchEvent
用来处理事件。
返回true则表示该View能处理该事件,事件将终止向上传递(传递给其父View);
返回false表示不能处理,则把事件传递给其父View的onTouchEvent()方法来处理

根据上面的小故事,我们用代码去实现一下:

我新建了四个类:MainActivity(开发经理),FirstFrameLayout(开发组长),SecondRelativeLayout(程序员),ThirdTextView(实习生)

在界面的层次关系如下图:

Android Touch事件传递机制通俗讲解

我就好比我们前面的故事情况一,情况二那么模拟下:

情况一

首先我们按照上面的故事情况一那样,任务一级一级传递下去(将viewgroup的类的onInterceptTouchEvent都返回 false),然后事件就会一直传递到,ThirdTextview,最后,我们把它的onTouchEvent返回ture(含义是实习生处理成功 了)。我们点击蓝色区域。我们看下日志。

Android Touch事件传递机制通俗讲解

情况二

首先我们还是一级级传递下去(将viewgroup的类的onInterceptTouchEvent都返回false),然后是传递到 ThirdTextview,我们将它的onTouchEvent返回false(含义是实习生处理失败了),再讲 SecondRelativeLayout的onTouchEvent返回false(含义是程序员处理失败了),最后讲 FirstFrameLayout的onTouchEvent返回true(含义是组长处理成功了)。我们点击蓝色区域。我们看下日志,如下图。

Android Touch事件传递机制通俗讲解

注:上面两种情况的日志图,红色框代表第一个任务,黄色框代表第二个类似的任务。根据上图日志,我们画下传递图:

情况一事件传递图:

Android Touch事件传递机制通俗讲解

情况二事件传递图:


从上面两张事件传递图我们的出来一些结论:

1.事件是先有dispatchTouchEvent分派给下一级

2.要经过onInterceptTouchEvent是否需要拦截,不拦截传递给下一级,最终传递给view控件,

3.onTouchEvent方法中,在处理事件中,如果返回True,则表示能处理,传递将会终止。反着,不能,如果不能的话,这会返回上一级的onTouchEvent方法中,如果还是false,会一直到到上一层的onTouchEvent方法中。

好奇的我们会发现一个问题:图二中的黄色线,没有像红色线那样,先传递到最底层,然后再回滚回去,这是为什么呢?

答:就如我们故事中,他都知道了,底下人都不会去做了,那么他干嘛还分派给他呢,他就会自己做了,直接给onTouchEvent ,这就是事件传递中的“记忆”功能。我们是手指点击蓝色区域,Touch事件有两个,第一个ACTION_DOWN,第二个ACTION_UP,第一个 ACTION_DOWN事件向下传递到某View,它把事件继续传递交给它的子View,它会记录该事件是否被它下面的View给处理成功了,(怎么能知 道呢?如果该事件会再次被向上传递到我这里来由我的onTouchEvent来处理,那就说明下面的View都没能成功处理该事件);当第二个 ACTION_UP事件向下传递到该View,该View的dispatchTouchEvent方法机会判断,若上次的事件由下面的view成功处理 了,那么这次的事件就继续交给下面的来处理,若上次的事件没有被下面的处理成功,那么这次的事件就不会向下传递了,该View直接调用自己的 onTouchEvent方法来处理该事件。

PS:关于这“记忆”功能的信息只在一系列事件完成之前有效,也就是从ACTION_DOWN事件开始,直到后续事件 ACTION_MOVE,ACTION_UP结束后,“记忆”的信息就会清除。也就是说如果某View处理ACTION_DOWN事件失败了 (onTouchEvent()返回false),那么后续的ACTION_MOVE,ACTION_UP等事件就不会再传递到该View了,由其父 View自己来处理。在下一次发生ACTION_DOWN事件的时候,还是会传递到该View的。

补充说明:

-》若在向下传递的过程中被拦截了,即onInterceptTouchEvent方法返回true,则事件将停止向下传递,直接由当前的onTouchEvent方法来处理,若处理成功则OK,若处理不成功,则事件会向上传递。

谢谢收看。

实例代码:http://download.csdn.net/detail/chenjie_920/8398413

Android Touch事件传递机制详解 


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

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

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

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

 

[java]  view plain  copy print?在CODE上查看代码片派生到我的代码片
  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方法中。

 

[java]  view plain  copy print?在CODE上查看代码片派生到我的代码片
  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方法:

 

[java]  view plain  copy print?在CODE上查看代码片派生到我的代码片
  1. @Override  
  2.     public boolean superDispatchTouchEvent(MotionEvent event) {  
  3.         //mDecor是一个DecorView类型变量  
  4.         return mDecor.superDispatchTouchEvent(event);  
  5.     }  


进入DecorView中的superDispatchTouchEvent方法:

[java]  view plain  copy print?在CODE上查看代码片派生到我的代码片
  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中查看此方法吧:

 

 

[java]  view plain  copy print?在CODE上查看代码片派生到我的代码片
  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,这个方法是干什么的呢,我们先看看他都干了什么吧

 

[java]  view plain  copy print?在CODE上查看代码片派生到我的代码片
  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方法吧

 

[java]  view plain  copy print?在CODE上查看代码片派生到我的代码片
  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是什么?

 

[java]  view plain  copy print?在CODE上查看代码片派生到我的代码片
  1. public void setOnTouchListener(OnTouchListener l) {  
  2.        mOnTouchListener = l;  
  3.    }  


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

 

[java]  view plain  copy print?在CODE上查看代码片派生到我的代码片
  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方法)。

+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

在Android系统中,和Touch事件分发和处理紧密相关的三个函数如下:
(1) public boolean dispatchTouchEvent(MotionEvent ev)
(2) public boolean onInterceptTouchEvent(MotionEvent ev)
(3) public boolean onTouchEvent(MotionEvent event)


这三个方法我在前一篇文章中都对他们的源码进行了分析:方法1主要是对Touch事件进行分发,方法2主要是对Touch事件进行拦截,方法3是对Touch事件进行处理

这三个方法主要存在于ViewGroup,View,Activity中,具体情况如下图:

 

 

 

ViewGroup

View

Activity

dispatchTouchEvent

onInterceptTouchEvent

onTouchEvent

下面我们就使用一个demo来看看这些方法的执行流程:
自定义一个类:MyLayoutFirst.java

 

[java]  view plain  copy print?
  1. public class MyLayoutFirst extends LinearLayout  
  2. {  
  3.   private static final String TAG = "MyLayoutFirst";  
  4.   public MyLayoutFirst(Context context, AttributeSet attrs)  
  5.   {  
  6.     super(context, attrs);  
  7.   }  
  8.     
  9.   @Override  
  10.   public boolean onInterceptTouchEvent(MotionEvent ev)  
  11.   {  
  12.     Log.w("yzy", "MyLayoutFirst->onInterceptTouchEvent->"+MyUtils.getActionName(ev));  
  13.     return super.onInterceptTouchEvent(ev);  
  14.   }  
  15.     
  16.   @Override  
  17.   public boolean onTouchEvent(MotionEvent event)  
  18.   {  
  19.     Log.e("yzy", "MyLayoutFirst->onTouchEvent->"+MyUtils.getActionName(event));  
  20.     return super.onTouchEvent(event);  
  21.   }  
  22.     
  23.   @Override  
  24.   public boolean dispatchTouchEvent(MotionEvent ev)  
  25.   {  
  26.     Log.i("yzy", "MyLayoutFirst->dispatchTouchEvent->"+MyUtils.getActionName(ev));  
  27.     return super.dispatchTouchEvent(ev);  
  28.   }  
  29.     
  30. }  


自定义一个类;MyLayoutSecond.java

 

[java]  view plain  copy print?
  1. public class MyLayoutSecond extends LinearLayout  
  2. {  
  3.   private static final String TAG = "MyLayoutSecond";  
  4.   public MyLayoutSecond(Context context, AttributeSet attrs)  
  5.   {  
  6.     super(context, attrs);  
  7.   }  
  8.     
  9.   @Override  
  10.   public boolean onTouchEvent(MotionEvent event)  
  11.   {  
  12.     Log.e("yzy", "MyLayoutSecond->MyLayoutSecond->"+MyUtils.getActionName(event));  
  13.     return super.onTouchEvent(event);  
  14.   }  
  15.     
  16.   @Override  
  17.   public boolean onInterceptTouchEvent(MotionEvent ev)  
  18.   {  
  19.     Log.w("yzy", "MyLayoutSecond->onInterceptTouchEvent->"+MyUtils.getActionName(ev));  
  20.     return super.onInterceptTouchEvent(ev);  
  21.   }  
  22.     
  23.   @Override  
  24.   public boolean dispatchTouchEvent(MotionEvent ev)  
  25.   {  
  26.     Log.i("yzy", "MyLayoutSecond->dispatchTouchEvent->"+MyUtils.getActionName(ev));  
  27.     return super.dispatchTouchEvent(ev);  
  28.   }  
  29.   
  30.    
  31. }  


加入到main_layout.xml中

 

[html]  view plain  copy print?
  1. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  2.     xmlns:tools="http://schemas.android.com/tools"  
  3.     android:layout_width="match_parent"  
  4.     android:layout_height="match_parent"  
  5.    >  
  6.   
  7.     <com.event.demo.MyLayoutFirst   
  8.         android:id="@+id/layout_first"  
  9.         android:layout_width="match_parent"  
  10.         android:layout_height="match_parent"  
  11.         android:background="#FF0000"  
  12.         >  
  13.         <com.event.demo.MyLayoutSecond  
  14.             android:id="@+id/layout_second"  
  15.             android:layout_width="320dip"  
  16.             android:layout_height="120dip"  
  17.             android:layout_gravity="center"  
  18.             android:background="#0000FF"  
  19.         >  
  20.           
  21.     </com.event.demo.MyLayoutSecond>  
  22.     </com.event.demo.MyLayoutFirst>  
  23.   
  24. </RelativeLayout>  


MainActivity中加入onTouchEvent方法

 

[java]  view plain  copy print?
  1. public class MainActivity extends Activity  
  2. {  
  3.   
  4.   @Override  
  5.   protected void onCreate(Bundle savedInstanceState)  
  6.   {  
  7.     super.onCreate(savedInstanceState);  
  8.     setContentView(R.layout.activity_main);  
  9.   }  
  10.   
  11.   @Override  
  12.   public boolean onCreateOptionsMenu(Menu menu)  
  13.   {  
  14.     // Inflate the menu; this adds items to the action bar if it is present.  
  15.     getMenuInflater().inflate(R.menu.main, menu);  
  16.     return true;  
  17.   }  
  18.     
  19.   @Override  
  20.   public boolean dispatchTouchEvent(MotionEvent ev)  
  21.   {  
  22.     Log.i("yzy", "MainActivity->dispatchTouchEvent->"+MyUtils.getActionName(ev));  
  23.     return super.dispatchTouchEvent(ev);  
  24.   }  
  25.     
  26.   @Override  
  27.   public boolean onTouchEvent(MotionEvent event)  
  28.   {  
  29.     Log.e("yzy", "MainActivity->onTouchEvent->"+MyUtils.getActionName(event));  
  30.     return super.onTouchEvent(event);  
  31.   }  
  32. }  


最后就一个工具类,用来将事件id转换为字符串。

 

[java]  view plain  copy print?
  1. public class MyUtils  
  2. {  
  3.   private static final String TAG = "MyUtils";  
  4.   public static String getActionName(MotionEvent event)  
  5.   {  
  6.     String name="";  
  7.     switch(event.getAction())  
  8.     {  
  9.       case MotionEvent.ACTION_DOWN:  
  10.         name="ACTION_DOWN";  
  11.         break;  
  12.       case MotionEvent.ACTION_MOVE:  
  13.         name="ACTION_MOVE";  
  14.         break;  
  15.       case MotionEvent.ACTION_UP:  
  16.         name="ACTION_UP";  
  17.         break;  
  18.     }  
  19.     return name;  
  20.   }  
  21. }  


运行效果如图:

 

其中蓝色部分是MyLayoutSecond.java ,红色部分是MyLayoutFirst.java
现在我点击一下蓝色部分:运行结果如图:

从图中可以看出,事件最先被Activity捕获,然后分发给 MyLayoutFirst,MyLayoutFirst首先调用自身的onInterceptTouchEvent判断是否将该事件拦截,由于默认返回是false,所以没有拦截,从而事件分发给了MyLayoutSecond,MyLayoutSecond同样通过dispatchTouchEvent分发出去,分发出去之前同样检查是否被拦截,默认都是没有被拦截的,但是由于MyLayoutSecond是没有子视图的,所有最终事件有自己处理,调用自身的onTouchEvent方法,由于该方法默认返回的是false,所以认为此事件是没有被消费掉的,继续传递到了MyLayoutFirst中,同样也没有消费这个事件,最终传递到了Mainactivity,继续往后看发现后面的ACTION_MOVE和ACTION_UP并没有传入MyLayoutFirst和MyLayoutSecond,这是因为一旦某一个事件没有被处理,后面的事件是不会被分发的。所以ACTION_MOVE和ACTION_UP直接被MainActivity处理掉了。

下面再看第二种情况:

 

 

MainActivity

MyLayoutFirst

MyLayoutSecond

dispatchTouchEvent

super.dispatchTouchEvent

super.dispatchTouchEvent

super.dispatchTouchEvent

onInterceptTouchEvent

--

true

super.onInterceptTouchEvent(ev)

onTouchEvent

super.onTouchEvent

super.onTouchEvent

super.onTouchEvent

运行结果如下:

从图中可以看出,事件传递到了MyLayoutFirst后没有分发到MyLayoutSecond,直接调用自身的onTouchEvent,由于返回的是false,导致事件没有消费,最终传递给了MainActivity,
而且后续事件也没有传递到MyLayoutFirst和MyLayoutSecond,直接被MainActivity处理

 

第三种情况:

 

MainActivity

MyLayoutFirst

MyLayoutSecond

dispatchTouchEvent

super.dispatchTouchEvent

super.dispatchTouchEvent

super.dispatchTouchEvent

onInterceptTouchEvent

--

true

super.onInterceptTouchEvent(ev)

onTouchEvent

super.onTouchEvent

true

super.onTouchEvent

运行结果:

和情况二不同的是MyLayoutFirst的onTouchEvent返回了true,也就是说MyLayoutFirst消费了此事件,所以ACTION_DOWN也没有再传给MainActivity,并且ACTION_MOVE和ACTION_UP
均传给了MyLayoutFirst

 

第四中情况:

 

MainActivity

MyLayoutFirst

MyLayoutSecond

dispatchTouchEvent

super.dispatchTouchEvent

super.dispatchTouchEvent

super.dispatchTouchEvent

onInterceptTouchEvent

--

super.onInterceptTouchEvent(ev)

super.onInterceptTouchEvent(ev)

onTouchEvent

super.onTouchEvent

super.onTouchEven

true

运行结果:

发现所有的事件都是传递到了MyLayoutSecond后被消费了

 

其实还有很多其他组合方式,大家如果又兴趣可以自己尝试改变每个函数的返回值,查看打印结果,这里我就不一一列举了。。。。。


最后我会提供一个小demo演示如何解决滑动冲突,背景如下:
一个ViewPager里面包含两个Framgent,有一个Fragment里面有一个HorizontalListView ,如何滑动冲突?
我就贴出关键代码吧

 

 

[java]  view plain  copy print?
  1. horizontal=(HorizontalListView)view.findViewById(R.id.hscroll);  
  2.     horizontal.setOnTouchListener(new OnTouchListener()  
  3.     {  
  4.         
  5.       @Override  
  6.       public boolean onTouch(View arg0, MotionEvent event)  
  7.       {  
  8.         if(event.getAction()==MotionEvent.ACTION_DOWN)  
  9.         {  
  10.           parent.requestDisallowInterceptTouchEvent(true);  
  11.         }else if(event.getAction()==MotionEvent.ACTION_UP)  
  12.         {  
  13.           parent.requestDisallowInterceptTouchEvent(false);  
  14.         }  
  15.         return false;  
  16.       }  
  17.     });  


加入这段代码就可以避免滑动冲突了,至于为什么大家可以参考我的前以前文章《Android Touch 事件传递机制详解 上》 这两个demo的例子我均会上传下载的

失败是什么?没有什么,只是更走近成功一步;成功是什么?就是走过了所有通向失败的路,只剩下一条路,那就是成功的路。

https://pianshen.com/article/44151541335/

相关信息

评论