Android事件传递机制

本文深入解析Android中事件分发的重要性和难点,通过详细解释dispatchTouchEvent()和onTouchEvent()方法的工作原理,以及事件流的走向,帮助开发者理解事件如何在不同组件间传递和处理。重点分析了事件中断、消费、回溯的机制,以及如何利用onInterceptTouchEvent()方法进行事件拦截,提供清晰的事件分发流程图和关键代码片段,旨在帮助开发者巩固基础知识并解决实际开发中遇到的问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在Android 开发中事件分发是比较重要的,也是比较难理解的,之前看过这方面的东西,以为自己弄懂了,也就没太注意,最近面试呢,想着肯定要问到这一块的东西,回顾的时候发现又忘了,真是好记性不如烂笔头啊,长期没有用到这一块的东西,也就抛之脑后了,今天用了半天时间,看资料有了解了一下,把这块内容整理一下,省的以后在以后学习过程中,要不断温故一下,看的时候就不用那么麻烦了。

      文章参考于:http://www.jianshu.com/p/e99b5e8bd67b

dispatchTouchEvent()方法的返回值取决于li.mOnTouchListener.onTouch(this, event) || onTouchEvent(event)) 的返回值.

View的onTouchEvent()返回false会执行父布局的onTouchEvent(), 反之不会执行.

      下面就是事件分发的流程图:

      

 

  • 仔细看的话,图分为3层,从上往下依次是Activity、ViewGroup、View
  • 事件从左上角那个白色箭头开始,由Activity的dispatchTouchEvent做分发
  • 箭头的上面字代表方法返回值,(return true、return false、return super.xxxxx(),super 的意思是调用父类实现。
  • dispatchTouchEvent和 onTouchEvent的框里有个【true---->消费】的字,表示的意思是如果方法返回true,那么代表事件就此消费,不会继续往别的地方传了,事件终止。
  • 目前所有的图的事件是针对ACTION_DOWN的,对于ACTION_MOVE和ACTION_UP我们最后做分析。
  • 图中的Activity 的dispatchTouchEvent ,只有return super.dispatchTouchEvent(ev) 才是往下走,返回true 或者 false 事件就被消费了(终止传递)。

 

  

     仔细看整个图,我们得出事件流 走向的几个结论(希望读者专心的看下图 1,多看几遍,脑子有比较清晰的概念。)
     1、如果事件不被中断,整个事件流向是一个类U型图,我们来看下这张图,可能更能理解U型图的意思。
 

    

     

   

所以如果我们没有对控件里面的方法进行重写或更改返回值,而直接用super调用父类的默认实现,那么整个事件流向应该是从Activity---->ViewGroup--->View 从上往下调用dispatchTouchEvent方法,一直到叶子节点(View)的时候,再由View--->ViewGroup--->Activity从下往上调用onTouchEvent方法。

 

2、dispatchTouchEvent 和 onTouchEvent 一旦return true,事件就停止传递了(到达终点)(没有谁能再收到这个事件)。看下图中只要return true事件就没再继续传下去了,对于return true我们经常说事件被消费了,消费了的意思就是事件走到这里就是终点,不会往下传,没有谁能再收到这个事件了

 

 

 

    3、dispatchTouchEvent 和 onTouchEvent return false的时候事件都回传给父控件的onTouchEvent处理。

 

   

     看上图深蓝色的线,对于返回false的情况,事件都是传给父控件onTouchEvent处理。

  • 对于dispatchTouchEvent 返回 false 的含义应该是:事件停止往子View传递和分发同时开始往父控件回溯(父控件的onTouchEvent开始从下往上回传直到某个onTouchEvent return true),事件分发机制就像递归,return false 的意义就是递归停止然后开始回溯。
  • 对于onTouchEvent return false 就比较简单了,它就是不消费事件,并让事件继续往父控件的方向从下往上流动。

  

4、dispatchTouchEvent、onTouchEvent、onInterceptTouchEvent
ViewGroup 和View的这些方法的默认实现就是会让整个事件按照U型完整走完,所以 return super.xxxxxx() 就会让事件依照U型的方向的完整走完整个事件流动路径),中间不做任何改动,不回溯、不终止,每个环节都走到。

 

所以如果看到方法return super.xxxxx() 那么事件的下一个流向就是走U型下一个目标,稍微记住上面这张图,你就能很快判断出下一个走向是哪个控件的哪个函数。

 


5、onInterceptTouchEvent 的作用

 

 

Intercept 的意思就拦截,每个ViewGroup每次在做分发的时候,问一问拦截器要不要拦截(也就是问问自己这个事件要不要自己来处理)如果要自己处理那就在onInterceptTouchEvent方法中 return true就会交给自己的onTouchEvent的处理,如果不拦截就是继续往子控件往下传。默认是不会去拦截的,因为子View也需要这个事件,所以onInterceptTouchEvent拦截器return super.onInterceptTouchEvent()和return false是一样的,是不会拦截的,事件会继续往子View的dispatchTouchEvent传递

6、ViewGroup 和View 的dispatchTouchEvent方法返回super.dispatchTouchEvent()的时候事件流走向。



 

首先看下ViewGroup 的dispatchTouchEvent,之前说的return true是终结传递。return false 是回溯到父View的onTouchEvent,然后ViewGroup怎样通过dispatchTouchEvent方法能把事件分发到自己的onTouchEvent处理呢,return true和false 都不行,那么只能通过Interceptor把事件拦截下来给自己的onTouchEvent,所以ViewGroup dispatchTouchEvent方法的super默认实现就是去调用onInterceptTouchEvent,记住这一点
那么对于View的dispatchTouchEvent return super.dispatchTouchEvent()的时候呢事件会传到哪里呢,很遗憾View没有拦截器。但是同样的道理return true是终结。return false 是回溯会父类的onTouchEvent,怎样把事件分发给自己的onTouchEvent 处理呢,那只能return super.dispatchTouchEvent,View类的dispatchTouchEvent()方法默认实现就是能帮你调用View自己的onTouchEvent方法的。

 

说了这么多,不知道有说清楚没有,我这边最后总结一下:

  • 对于 dispatchTouchEvent,onTouchEvent,return true是终结事件传递。return false 是回溯到父View的onTouchEvent方法。
  • ViewGroup 想把自己分发给自己的onTouchEvent,需要拦截器onInterceptTouchEvent方法return true 把事件拦截下来。
  • ViewGroup 的拦截器onInterceptTouchEvent 默认是不拦截的,所以return super.onInterceptTouchEvent()=return false;
  • View 没有拦截器,为了让View可以把事件分发给自己的onTouchEvent,View的dispatchTouchEvent默认实现(super)就是把事件分发给自己的onTouchEvent。

ViewGroup和View 的dispatchTouchEvent 是做事件分发,那么这个事件可能分发出去的四个目标

注:------> 后面代表事件目标需要怎么做。
1、 自己消费,终结传递。------->return true ;
2、 给自己的onTouchEvent处理-------> 调用super.dispatchTouchEvent()系统默认会去调用 onInterceptTouchEvent,在onInterceptTouchEvent return true就会去把事件分给自己的onTouchEvent处理。
3、 传给子View------>调用super.dispatchTouchEvent()默认实现会去调用 onInterceptTouchEvent 在onInterceptTouchEvent return false,就会把事件传给子类。
4、 不传给子View,事件终止往下传递,事件开始回溯,从父View的onTouchEvent开始事件从下到上回归执行每个控件的onTouchEvent------->return false;
注: 由于View没有子View所以不需要onInterceptTouchEvent 来控制是否把事件传递给子View还是拦截,所以View的事件分发调用super.dispatchTouchEvent()的时候默认把事件传给自己的onTouchEvent处理(相当于拦截),对比ViewGroup的dispatchTouchEvent 事件分发,View的事件分发没有上面提到的4个目标的第3点。

ViewGroup和View的onTouchEvent方法是做事件处理的,那么这个事件只能有两个处理方式:

1、自己消费掉,事件终结,不再传给谁----->return true;
2、继续从下往上传,不消费事件,让父View也能收到到这个事件----->return false;View的默认实现是不消费的。所以super==false。

ViewGroup的onInterceptTouchEvent方法对于事件有两种情况:

1、拦截下来,给自己的onTouchEvent处理--->return true;
2、不拦截,把事件往下传给子View---->return false,ViewGroup默认是不拦截的,所以super==false;

 

一、View的dispatchTouchEvent和onTouchEvent

对于View来说,事件传递机制有两个函数:dispatchTouchEvent负责分发事件,在dispatch***里又会调用onTouchEvent表示执行事件,或者说消费事件,结合源码分析其流程。事件传递的入口是View的dispatchTouchEvent()函数:

[java] view plain copy  print

  1.      * Pass the touch screen motion event down to the target view, or this 
  2.      * view if it is the target. 
  3.      * 
  4.      * @param event The motion event to be dispatched. 
  5.      * @return True if the event was handled by the view, false otherwise. 
  6.      */  
  7.     public boolean dispatchTouchEvent(MotionEvent event) {  
  8.         if (mInputEventConsistencyVerifier != null) {  
  9.             mInputEventConsistencyVerifier.onTouchEvent(event, 0);  
  10.         }  
  11.   
  12.         if (onFilterTouchEventForSecurity(event)) {  
  13.             //noinspection SimplifiableIfStatement  
  14.             ListenerInfo li = mListenerInfo;  
  15.             if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED  
  16.                     && li.mOnTouchListener.onTouch(this, event)) {  
  17.                 return true;  
  18.             }  
  19.   
  20.             if (onTouchEvent(event)) {  
  21.                 return true;  
  22.             }  
  23.         }  
  24.   
  25.         if (mInputEventConsistencyVerifier != null) {  
  26.             mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);  
  27.         }  
  28.         return false;  
  29.     }

找到这个判断:

            if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) {
                return true;
            }

他会执行View的OnTouchListener.onTouch这个函数,也就是上面说的onTouch监听。里面有三个判断,如果三个都为1,就会执行return true,不往下走了。而默认的onTouch监听返回false,只要一个是false,就不会返回true。接着往下看,程序执行onTouchEvent:

[java] view plain copy  print

  1.  if (onTouchEvent(event)) {  
  2.                 return true;  
  3.  } 

onTouchEvent的源码比较多,贴最重要的:

[java] view plain copy  print

  1.  if (!mHasPerformedLongPress) {  
  2.                             // This is a tap, so remove the longpress check  
  3.                             removeLongPressCallback();  
  4.   
  5.                             // Only perform take click actions if we were in the pressed state  
  6.                             if (!focusTaken) {  
  7.                                 // Use a Runnable and post this rather than calling  
  8.                                 // performClick directly. This lets other visual state  
  9.                                 // of the view update before click actions start.  
  10.                                 if (mPerformClick == null) {  
  11.                                     mPerformClick = new PerformClick();  
  12.                                 }  
  13.                                 if (!post(mPerformClick)) {  
  14.                                    performClick();
  15.                                 }  
  16.                             }  
  17.                         }

可以看到有个performClick(),它的源码里有这么一句 li.mOnClickListener.onClick(this);

[java] view plain copy  print

  1. public boolean performClick() {  
  2.         sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);  
  3.   
  4.         ListenerInfo li = mListenerInfo;  
  5.         if (li != null && li.mOnClickListener != null) {  
  6.             playSoundEffect(SoundEffectConstants.CLICK);  
  7.             li.mOnClickListener.onClick(this);  
  8.             return true;  
  9.         } 
  10.         return false;  
  11.     }


终于对上了,它执行了我们注册的onClick监听。当然执行前会经过一系列判断,是否注册了监听等。
总结:

1、事件入口是dispatchTouchEvent(),它会先执行注册的onTouch监听,如果一切顺利的话,接着执行onTouchEvent,在onTouchEvent里会执行onClick监听。

2、无论是dispatchTouchEvent还是onTouchEvent,如果返回true表示这个事件已经被消费、处理了,不再往下传了。在dispathTouchEvent的源码里可以看到,如果onTouchEvent返回了true,那么它也返回true。如果dispatch***在执行onTouch监听的时候,onTouch返回了true,那么它也返回true,这个事件提前被onTouch消费掉了。就不再执行onTouchEvent了,更别说onClick监听了。

3、我们通常在onTouch监听了设置图片一旦被触摸就改变它的背景、透明度之类的,这个onTouch表示事件的时机。而在onClick监听了去具体干某些事。

 

二、ViewGroup的dispatchTouchEvent、onInterceptTouchEvent、onTouchEvent

再来看ViewGroup,在复写ViewGroup时可以发现它的onTouchEvent在在View里的,表示这两个方法是一样的。但dispatchTouchEvent是在ViewGroup里的,表示和View的dispatchTouchEvent不一样,多了一个onInterceptTouchEvent函数,表示拦截的意思。链接 打个很形象的比喻,这玩意就像个秘书、谋士。为啥View没有呢,因为它级别不够,一个Button里面是不可能有子View的。但LinearLayout(继承ViewGroup)就有孩子(子布局),这个onInterceptTouchEvent就会判断事件要不要通知它的孩子呢。它的源码如下:

[java] view plain copy  print

  1. public boolean dispatchTouchEvent(MotionEvent ev) {  
  2.         if (mInputEventConsistencyVerifier != null) {  
  3.             mInputEventConsistencyVerifier.onTouchEvent(ev, 1);  
  4.         }  
  5.   
  6.         boolean handled = false;  
  7.         if (onFilterTouchEventForSecurity(ev)) {  
  8.             final int action = ev.getAction();  
  9.             final int actionMasked = action & MotionEvent.ACTION_MASK;  
  10.   
  11.             // Handle an initial down.  
  12.             if (actionMasked == MotionEvent.ACTION_DOWN) {  
  13.                 // Throw away all previous state when starting a new touch gesture.  
  14.                 // The framework may have dropped the up or cancel event for the previous gesture  
  15.                 // due to an app switch, ANR, or some other state change.  
  16.                 cancelAndClearTouchTargets(ev);  
  17.                 resetTouchState();  
  18.             }  
  19.   
  20.             // Check for interception.  
  21.             final boolean intercepted;  
  22.             if (actionMasked == MotionEvent.ACTION_DOWN  
  23.                     || mFirstTouchTarget != null) {  
  24.                 final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;  
  25.                 if (!disallowIntercept) {  
  26.                     intercepted = onInterceptTouchEvent(ev);
  27.                     ev.setAction(action); // restore action in case it was changed  
  28.                 } else {  
  29.                     intercepted = false;  
  30.                 }  
  31.             } else {  
  32.                 // There are no touch targets and this action is not an initial down  
  33.                 // so this view group continues to intercept touches.  
  34.                 intercepted = true;  
  35.             }  
  36.   
  37.             // Check for cancelation.  
  38.             final boolean canceled = resetCancelNextUpFlag(this)  
  39.                     || actionMasked == MotionEvent.ACTION_CANCEL;  
  40.   
  41.             // Update list of touch targets for pointer down, if needed.  
  42.             final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;  
  43.             TouchTarget newTouchTarget = null;  
  44.             boolean alreadyDispatchedToNewTouchTarget = false;  
  45.             if (!canceled && !intercepted) {  
  46.                 if (actionMasked == MotionEvent.ACTION_DOWN  
  47.                         || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)  
  48.                         || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {  
  49.                     final int actionIndex = ev.getActionIndex(); // always 0 for down  
  50.                     final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)  
  51.                             : TouchTarget.ALL_POINTER_IDS;  
  52.   
  53.                     // Clean up earlier touch targets for this pointer id in case they  have become out of sync.  
  54.                     removePointersFromTouchTargets(idBitsToAssign);  
  55.   
  56.                     final int childrenCount = mChildrenCount;  
  57.                     if (childrenCount != 0) {  
  58.                         // Find a child that can receive the event.  Scan children from front to back.  
  59.                         final View[] children = mChildren;  
  60.                         final float x = ev.getX(actionIndex);  
  61.                         final float y = ev.getY(actionIndex);  
  62.   
  63.                         final boolean customOrder = isChildrenDrawingOrderEnabled();  
  64.                         for (int i = childrenCount - 1; i >= 0; i--) {  
  65.                             final int childIndex = customOrder ?  
  66.                                     getChildDrawingOrder(childrenCount, i) : i;  
  67.                             final View child = children[childIndex];  
  68.                             if (!canViewReceivePointerEvents(child)  
  69.                                     || !isTransformedTouchPointInView(x, y, child, null)) {  
  70.                                 continue;  
  71.                             }  
  72.   
  73.                             newTouchTarget = getTouchTarget(child);  
  74.                             if (newTouchTarget != null) {  
  75.                                 // Child is already receiving touch within its bounds. Give it the new pointer in addition to the ones it is handling.  
  76.                                 newTouchTarget.pointerIdBits |= idBitsToAssign;  
  77.                                 break;  
  78.                             }  
  79.   
  80.                             resetCancelNextUpFlag(child);  
  81.                             if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {  
  82.                                 // Child wants to receive touch within its bounds.  
  83.                                 mLastTouchDownTime = ev.getDownTime();  
  84.                                 mLastTouchDownIndex = childIndex;  
  85.                                 mLastTouchDownX = ev.getX();  
  86.                                 mLastTouchDownY = ev.getY();  
  87.                                 newTouchTarget = addTouchTarget(child, idBitsToAssign);  
  88.                                 alreadyDispatchedToNewTouchTarget = true;  
  89.                                 break;  
  90.                             }  
  91.                         }  
  92.                     }  
  93.   
  94.                     if (newTouchTarget == null && mFirstTouchTarget != null) {  
  95.                         // Did not find a child to receive the event.  Assign the pointer to the least recently added target.  
  96.                         newTouchTarget = mFirstTouchTarget;  
  97.                         while (newTouchTarget.next != null) {  
  98.                             newTouchTarget = newTouchTarget.next;  
  99.                         }  
  100.                         newTouchTarget.pointerIdBits |= idBitsToAssign;  
  101.                     }  
  102.                 }  
  103.             }  
  104.   
  105.             // Dispatch to touch targets.  
  106.             if (mFirstTouchTarget == null) {  
  107.                 // No touch targets so treat this as an ordinary view.  
  108.                 handled = dispatchTransformedTouchEvent(ev, canceled, null,  
  109.                         TouchTarget.ALL_POINTER_IDS);  
  110.             } else {  
  111.                 // Dispatch to touch targets, excluding the new touch target if we already  
  112.                 // dispatched to it.  Cancel touch targets if necessary.  
  113.                 TouchTarget predecessor = null;  
  114.                 TouchTarget target = mFirstTouchTarget;  
  115.                 while (target != null) {  
  116.                     final TouchTarget next = target.next;  
  117.                     if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {  
  118.                         handled = true;  
  119.                     } else {  
  120.                         final boolean cancelChild = resetCancelNextUpFlag(target.child)  
  121.                         || intercepted;  
  122.                         if (dispatchTransformedTouchEvent(ev, cancelChild,  
  123.                                 target.child, target.pointerIdBits)) {  
  124.                             handled = true;  
  125.                         }  
  126.                         if (cancelChild) {  
  127.                             if (predecessor == null) {  
  128.                                 mFirstTouchTarget = next;  
  129.                             } else {  
  130.                                 predecessor.next = next;  
  131.                             }  
  132.                             target.recycle();  
  133.                             target = next;  
  134.                             continue;  
  135.                         }  
  136.                     }  
  137.                     predecessor = target;  
  138.                     target = next;  
  139.                 }  
  140.             }  
  141.   
  142.             // Update list of touch targets for pointer up or cancel, if needed.  
  143.             if (canceled  
  144.                     || actionMasked == MotionEvent.ACTION_UP  
  145.                     || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {  
  146.                 resetTouchState();  
  147.             } else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {  
  148.                 final int actionIndex = ev.getActionIndex();  
  149.                 final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);  
  150.                 removePointersFromTouchTargets(idBitsToRemove);  
  151.             }  
  152.         }  
  153.   
  154.         if (!handled && mInputEventConsistencyVerifier != null) {  
  155.             mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);  
  156.         }  
  157.         return handled;  
  158.     }


可以看到标红的有两句(intercepted = onInterceptTouchEvent(ev);    if (!canceled && !intercepted)  ),它会先调用 intercepted = onInterceptTouchEvent(ev);然后通过if判断。

[java] view plain copy  print

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


它就一句话,默认false。也就是说这个谋士默认的意见是,永远不拦截!!!!只要有孩子,就交给孩子们处理吧。

最后的结论:

1、如果是自定义复合控件,如图片+文字,我再Activity里给你注册了onClick监听,期望点击它执行。那么最简单的方法就是将图片+文字的父布局,也即让其容器ViewGroup的秘书将事件拦下,这样父亲就可以执行onClick了。这时候的父亲就像一个独立的孩子一样了(View),无官一身轻,再也不用管它的孩子了,可以正常onClick onTouch.

2、如果希望一个View只onTouch而不onClick,在onTouch里return true就ok了。

3、dispatch是为了onTouch监听,onTouchEvent是为了onClick监听。

4、自定义布局时,一般情况下:

@Override
public boolean onTouchEvent(MotionEvent event) {

     return super.onTouchEvent(event);

}  

 

@Override
public boolean dispatchTouchEvent(MotionEvent event) {

     return super.dispatchTouchEvent(event);

}

我们可以复写,但是最后的super.***是万万不能少滴。如果少了,表示连dispatch*** onTouchEvent压根就不调用了,事件就此打住。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值