首页 最新 热门 推荐

  • 首页
  • 最新
  • 热门
  • 推荐

Android Touch事件分发(源码分析)

  • 25-02-16 05:00
  • 4005
  • 8490
blog.csdn.net

Android一文让你轻松搞定Touch事件分发 

源码分析

下面,咱们一起通过源码,全面解析事件分发机制,即按顺序讲解:

  • Activity事件分发机制

  • ViewGroup事件分发机制

  • View事件分发机制

Activity事件分发机制

        Android事件分发机制首先会将点击事件传递到Activity中,具体是执行dispatchTouchEvent()进行事件分发。

Activity.dispatchTouchEvent()源码

  1. /**
  2.   * 创建人:帅次
  3.   * 创建时间:2021/7/5
  4.   * 功能:Activity.dispatchTouchEvent()
  5.   */
  6. public boolean dispatchTouchEvent(MotionEvent ev) {
  7.     if (ev.getAction() == MotionEvent.ACTION_DOWN) {
  8.  //在这里仅用于ACTION_DOWN的判断
  9.  onUserInteraction();
  10.     }
  11.     //返回true
  12.     if (getWindow().superDispatchTouchEvent(ev)) {
  13.   //Activity.dispatchTouchEvent()就返回true,则方法结束。
  14.    //该点击事件停止往下传递&事件传递过程结束(让爷爷吃了嘿嘿)
  15.    return true;
  16.     }
  17.     return onTouchEvent(ev);
  18. }

Activity.onUserInteraction()源码

  1. /**
  2.   * 创建人:帅次
  3.   * 创建时间:2021/7/6
  4.   * 功能:该方法是用户交互,每当向Activity分派按键、触摸或轨迹球事件时调用。
  5.   */
  6. public void onUserInteraction() {
  7. }

Window.superDispatchTouchEvent()源码

  1. /**
  2.   * 创建人:帅次
  3.   * 创建时间:2021/7/6
  4.   * 功能:Window.superDispatchTouchEvent属于抽象方法。
  5.   * 用于自定义窗口,如Dialog,传递触摸屏事件进一步向下视图层次结构。
  6.   * 应用程序开发人员应该不需要实现或调用它。
  7.   */
  8. public abstract boolean superDispatchTouchEvent(MotionEvent event);

因为Window是抽象类,咱就继续挖,就找到了它的唯一实现类「PhoneWindow」。

PhoneWindow.superDispatchTouchEvent()源码

  1. // This is the top-level view of the window, containing the window decor.
  2. //这是窗口的顶层View的实例对象。 
  3. private DecorView mDecor;
  4. @Override
  5. public boolean superDispatchTouchEvent(MotionEvent event) {
  6.  //咱们继续往下看
  7.  return mDecor.superDispatchTouchEvent(event);
  8. }

DecorView.superDispatchTouchEvent()源码

  1. public boolean superDispatchTouchEvent(MotionEvent event) {
  2.  //super调用父类dispatchTouchEvent方法。那它的父类是谁呢?
  3.  //DecorView extends FrameLayout、FrameLayout extends ViewGroup!
  4.  //从上面看出DecorView 是ViewGroup的间接子类。
  5.  //看到这里Activity.dispatchTouchEvent()也基本差不多了
  6.  //如果ViewGroup.dispatchTouchEvent return true,
  7.  //则Activity.dispatchTouchEvent()return true。
  8.  //如果ViewGroup.dispatchTouchEvent return false,
  9.  //则执行Activity.onTouchEvent(ev)。
  10.  return super.dispatchTouchEvent(event);
  11. }

Activity.onTouchEvent()源码

  1. /**
  2.   * 创建人:帅次
  3.   * 创建时间:2021/7/6
  4.   * 功能:当Touch事件未被其下的任何View消费时调用。
  5.   */
  6. public boolean onTouchEvent(MotionEvent event) {
  7.     //Window.shouldCloseOnTouch来判断是否消费。
  8.     //那咱就继续看看Window.shouldCloseOnTouch是干嘛的
  9.     if (mWindow.shouldCloseOnTouch(this, event)) {
  10.         finish();
  11.         //已经消费了该事件,则返回true
  12.         return true;
  13.     }
  14.     //还没有消费返回false,默认返回false
  15.     return false;
  16. }

Window.shouldCloseOnTouch()源码

  1. //这里支持的最高版本maxTargetSdk = Build.VERSION_CODES.P(28),咱使用SDK30就没办法处理了。
  2. //主要是对于处理边界外点击事件的判断:是否是DOWN事件,event的坐标是否在边界内等
  3. @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
  4. public boolean shouldCloseOnTouch(Context context, MotionEvent event) {
  5.     final boolean isOutside =
  6.             event.getAction() == MotionEvent.ACTION_UP && isOutOfBounds(context, event)
  7.             || event.getAction() == MotionEvent.ACTION_OUTSIDE;
  8.     if (mCloseOnTouchOutside && peekDecorView() != null && isOutside) {
  9.  //return true:说明事件在边界外,即 消费事件
  10.  return true;
  11.     }
  12. //返回false:在边界内,即未消费(默认)
  13.     return false;
  14. }

Activity.onTouchEvent()到这里就基本结束了。后面源码完善在再补充。

Activity源码总结

当一个点击事件发生时,从Activity的事件分发开始(Activity.dispatchTouchEvent()),流程如下:

ViewGroup事件分发机制

        从上面Activity的事件分发机制可知,在Activity.dispatchTouchEvent()实现了将事件从Activity->ViewGroup的传递,ViewGroup的事件分发机制从dispatchTouchEvent()开始。

        在Activity.dispatchTouchEvent()中遗留了ViewGroup.dispatchTouchEvent()什么时候返回true/false在下面的源码分析中找出来。

ViewGroup.dispatchTouchEvent()源码

先从宏观角度,纵览整个 dispatch 的源码如下:

  1. @Override
  2. public boolean onInterceptTouchEvent(MotionEvent ev) {
  3.     /**
  4.      * 一、检查当前ViewGroup是否需要拦截事件
  5.      * 1、如果事件为DOWN事件,则调用onInterceptTouchEvent进行拦截判断;
  6.      * 2、mFirstTouchTarget!=null,代表已经有子View捕获了这个事件,
  7.      子 View 的 dispatchTouchEvent 返回true就是代表捕获touch 件。
  8.      */
  9.     /**
  10.      * 二、将事件分发给子View
  11.      * 满足条件canceled和intercepted都为false,既不取消也不拦截
  12.      * 1、actionMasked==MotionEvent.ACTION_DOWN
  13.      表明事件主动分发的前提是事件为 DOWN 事件
  14.      * 2、通过for循环,遍历当前ViewGroup下的所有子View;
  15.      * 3、处判断事件坐标是否在子 View 坐标范围内,并且子 View 并没有处在动画状态;
  16.      * 4、调用 dispatchTransformedTouchEvent 方法将事件分发给子 View,
  17.      如果子 View 捕获事件成功,则将 View 赋值给 mFirstTouchTarget 。
  18.      */
  19.     /**
  20.      * 三、根据mFirstTouchTarget再次分发事件
  21.      * 3.1、mFirstTouchTarget为null,说明在上述的事件分发中并没有子 View 对事件进行了捕获操作。
  22.      直接调用 dispatchTransformedTouchEvent 方法,并传入child为 null
  23.      最终会调用 super.dispatchTouchEvent 方法。实际上最终会调用自身的 onTouchEvent 方法,进行处理touch事件。
  24.      结论:如果没有子 View 捕获处理 touch 事件,ViewGroup会通过自身的onTouchEvent方法进行处理。
  25.      * 3.2、mFirstTouchTarget 不为 null,说明在上述的事件分发中有子 View 对 touch 事件进行了捕获,
  26.      则直接将当前以及后续的事件交给 mFirstTouchTarget 指向的 View 进行处理。
  27.      */
  28. }

下面咱逐层分析:

  1. @Override
  2. public boolean dispatchTouchEvent(MotionEvent ev) {
  3.     // 是否按下操作 , 最终的对外返回结果 , 该方法的最终返回值 
  4.     boolean handled = false;
  5.     /*onFilterTouchEventForSecurity以应用安全策略过滤触摸事件。
  6.     /return true分派事件,return false删除事件。*/
  7.     if (onFilterTouchEventForSecurity(ev)) {
  8.         final int action = ev.getAction();
  9.         final int actionMasked = action & MotionEvent.ACTION_MASK;
  10.         final boolean intercepted;
  11.         /**
  12.          * 一、检查当前ViewGroup是否需要拦截事件
  13.          * 1、如果事件为DOWN事件,则调用onInterceptTouchEvent进行拦截判断;
  14.          * 2、mFirstTouchTarget!=null,代表已经有子View捕获了这个事件,
  15.          子 View 的 dispatchTouchEvent 返回true就是代表捕获touch 件。
  16.          */
  17.         if (actionMasked == MotionEvent.ACTION_DOWN
  18.                 || mFirstTouchTarget != null) {
  19.             final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
  20.             if (!disallowIntercept) {
  21.                 intercepted = onInterceptTouchEvent(ev);
  22.                 //恢复操作以防它被更改
  23.                 ev.setAction(action);
  24.             } else {
  25.                 intercepted = false;
  26.             }
  27.         } else {
  28.             // There are no touch targets and this action is not an initial down
  29.             // so this view group continues to intercept touches.
  30.             intercepted = true;
  31.         }
  32.         // Check for cancelation.
  33.         final boolean canceled = resetCancelNextUpFlag(this)
  34.                 || actionMasked == MotionEvent.ACTION_CANCEL;
  35.         // Update list of touch targets for pointer down, if needed.
  36.         final boolean isMouseEvent = ev.getSource() == InputDevice.SOURCE_MOUSE;
  37.         final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0
  38.                 && !isMouseEvent;
  39.         TouchTarget newTouchTarget = null;
  40.         boolean alreadyDispatchedToNewTouchTarget = false;
  41.         /**
  42.          * 二、将事件分发给子View
  43.          * 满足条件canceled和intercepted都为false,既不取消也不拦截
  44.          */
  45.         if (!canceled && !intercepted) {
  46.             /*1、actionMasked==MotionEvent.ACTION_DOWN表明
  47.             事件主动分发的前提是事件为 DOWN 事件*/
  48.             if (actionMasked == MotionEvent.ACTION_DOWN
  49.                     || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
  50.                     || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
  51.                 final int actionIndex = ev.getActionIndex(); // always 0 for down
  52.                 final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
  53.                         : TouchTarget.ALL_POINTER_IDS;
  54.                 // Clean up earlier touch targets for this pointer id in case they
  55.                 // have become out of sync.
  56.                 removePointersFromTouchTargets(idBitsToAssign);
  57.                 final int childrenCount = mChildrenCount;
  58.                 if (newTouchTarget == null && childrenCount != 0) {
  59.                     final float x =
  60.                             isMouseEvent ? ev.getXCursorPosition() : ev.getX(actionIndex);
  61.                     final float y =
  62.                             isMouseEvent ? ev.getYCursorPosition() : ev.getY(actionIndex);
  63.                     // Find a child that can receive the event.
  64.                     // Scan children from front to back.
  65.                     final ArrayList preorderedList = buildTouchDispatchChildList();
  66.                     final boolean customOrder = preorderedList == null
  67.                             && isChildrenDrawingOrderEnabled();
  68.                     final View[] children = mChildren;
  69.                     //2、通过for循环,遍历当前ViewGroup下的所有子View;
  70.                     for (int i = childrenCount - 1; i >= 0; i--) {
  71.                         final int childIndex = getAndVerifyPreorderedIndex(
  72.                                 childrenCount, i, customOrder);
  73.                         final View child = getAndVerifyPreorderedView(
  74.                                 preorderedList, children, childIndex);
  75.                         //3、处判断事件坐标是否在子 View 坐标范围内,并且子 View 并没有处在动画状态;
  76.                         if (!child.canReceivePointerEvents()
  77.                                 || !isTransformedTouchPointInView(x, y, child, null)) {
  78.                             continue;
  79.                         }
  80.                         newTouchTarget = getTouchTarget(child);
  81.                         if (newTouchTarget != null) {
  82.                             // Child is already receiving touch within its bounds.
  83.                             // Give it the new pointer in addition to the ones it is handling.
  84.                             newTouchTarget.pointerIdBits |= idBitsToAssign;
  85.                             break;
  86.                         }
  87.                         resetCancelNextUpFlag(child);
  88.                         /*4、调用 dispatchTransformedTouchEvent 方法将事件分发给子 View,
  89.                          如果子 View 捕获事件成功,则将 View 赋值给 mFirstTouchTarget 。*/
  90.                         if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
  91.                             // Child wants to receive touch within its bounds.
  92.                             mLastTouchDownTime = ev.getDownTime();
  93.                             if (preorderedList != null) {
  94.                                 // childIndex points into presorted list, find original index
  95.                                 for (int j = 0; j < childrenCount; j++) {
  96.                                     if (children[childIndex] == mChildren[j]) {
  97.                                         mLastTouchDownIndex = j;
  98.                                         break;
  99.                                     }
  100.                                 }
  101.                             } else {
  102.                                 mLastTouchDownIndex = childIndex;
  103.                             }
  104.                             mLastTouchDownX = ev.getX();
  105.                             mLastTouchDownY = ev.getY();
  106.                             newTouchTarget = addTouchTarget(child, idBitsToAssign);
  107.                             alreadyDispatchedToNewTouchTarget = true;
  108.                             break;
  109.                         }
  110.                     }
  111.                     if (preorderedList != null) preorderedList.clear();
  112.                 }
  113.             }
  114.         }
  115.         /**
  116.          * 三、根据mFirstTouchTarget再次分发事件
  117.          */
  118.         //3.1mFirstTouchTarget为null,
  119.         if (mFirstTouchTarget == null) {
  120.             /*说明在上述的事件分发中并没有子 View 对事件进行了捕获操作。
  121.             直接调用 dispatchTransformedTouchEvent 方法,并传入child为 null
  122.             最终会调用 super.dispatchTouchEvent 方法。
  123.             实际上最终会调用自身的 onTouchEvent 方法,进行处理touch事件。*/
  124.             /*结论:如果没有子 View 捕获处理 touch 事件,
  125.             ViewGroup会通过自身的onTouchEvent方法进行处理。*/
  126.             handled = dispatchTransformedTouchEvent(ev, canceled, null,
  127.                     TouchTarget.ALL_POINTER_IDS);
  128.         } else {
  129.             // Dispatch to touch targets, excluding the new touch target if we already
  130.             // dispatched to it.  Cancel touch targets if necessary.
  131.             TouchTarget predecessor = null;
  132.             TouchTarget target = mFirstTouchTarget;
  133.             while (target != null) {
  134.                 final TouchTarget next = target.next;
  135.                 if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
  136.                     handled = true;
  137.                 } else {
  138.                     final boolean cancelChild = resetCancelNextUpFlag(target.child)
  139.                             || intercepted;
  140.                     /*mFirstTouchTarget 不为 null,说明在上述的事件分发中有子 View 对 touch 事件进行了捕获,
  141.                     则直接将当前以及后续的事件交给 mFirstTouchTarget 指向的 View 进行处理。
  142.                      */
  143.                     if (dispatchTransformedTouchEvent(ev, cancelChild,
  144.                             target.child, target.pointerIdBits)) {
  145.                         handled = true;
  146.                     }
  147.                 }
  148.                 predecessor = target;
  149.                 target = next;
  150.             }
  151.         }
  152.     }
  153.     return handled;
  154. }

ViewGroup.onInterceptTouchEvent()源码

  1. /**
  2.   * 创建人:帅次
  3.   * 创建时间:2021/7/6
  4.   * 前提ViewGroup.dispatchTouchEvent return super.dispatchTouchEvent(ev);
  5.   * 功能:是否拦截事件 return true拦截,return false(默认)不拦截
  6.   */
  7. public boolean onInterceptTouchEvent(MotionEvent ev) {
  8.     if (ev.isFromSource(InputDevice.SOURCE_MOUSE)
  9.             && ev.getAction() == MotionEvent.ACTION_DOWN
  10.             && ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY)
  11.             && isOnScrollbarThumb(ev.getX(), ev.getY())) {
  12.         return true;
  13.     }
  14.     return false;
  15. }

ViewGroup.onTouchEvent()源码

        ViewGroup是没有onTouchEvent()这个方法的,因为ViewGroup是View的子类,所以ViewGroup可以直接调用View的onTouchEvent()方法。这里就不做详细介绍,下面在View事件分发机制中一起解答。

ViweGroup源码总结

        Android事件分发传递到Acitivity后,总是先传递到ViewGroup、再传递到View。流程总结如下:(假设已经经过了Acitivity事件分发传递并传递到ViewGroup)

View的事件分发机制

从上面ViewGroup事件分发机制知道,View事件分发机制从dispatchTouchEvent()开始。

View.dispatchTouchEvent()源码

  1. public boolean dispatchTouchEvent(MotionEvent event) {
  2.     /*一、(mViewFlags & ENABLED_MASK) == ENABLED
  3.      1、该条件是判断当前点击的控件是否enable
  4.      2、由于很多View默认enable,故该条件恒定为true(除非手动设置为false)
  5.      */
  6.     /*二、mOnTouchListener != null
  7.      1、mOnTouchListener变量在View.setOnTouchListener()里赋值
  8.      2、即只要给控件注册了Touch事件,mOnTouchListener就一定被赋值(即不为空)
  9.      */
  10.      /*三、mOnTouchListener.onTouch(this, event)
  11.      1、即回调控件注册Touch事件时的onTouch();
  12.      2、需手动复写设置,具体如下(以View为例)
  13.      view.setOnTouchListener(new OnTouchListener() {
  14.         @Override
  15.         public boolean onTouch(View v, MotionEvent event) {
  16.             MLog.logEvent("MyTouchTrueView.onTouch自行处理:",event);
  17.             return true;
  18.             // 1、若在onTouch()返回true,就会让上述三个条件全部成立,
  19.             从而使得View.dispatchTouchEvent()直接返回true,事件分发结束
  20.             // 2、若在onTouch()返回false,就会使得上述三个条件不全部成立,
  21.             从而使得View.dispatchTouchEvent()中跳出if,执行onTouchEvent(event)
  22.             // 3、onTouchEvent()源码分析 
  23.         }
  24.     });
  25.      */
  26.     if ( (mViewFlags & ENABLED_MASK) == ENABLED &&
  27.             mOnTouchListener != null &&
  28.             mOnTouchListener.onTouch(this, event)) {
  29.         return true;
  30.     }
  31.     return onTouchEvent(event);
  32. }

View.onTouchEvent()源码

  1. public boolean onTouchEvent(MotionEvent event) {  
  2.     ... // 仅展示关键代码
  3.     // 若该控件可点击,则进入switch判断中
  4.     if (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {  
  5.         // 根据当前事件类型进行判断处理
  6.         switch (event.getAction()) { 
  7.             // 抬起View
  8.             case MotionEvent.ACTION_UP:  
  9.                     performClick(); 
  10.                     break;  
  11.             // 按下
  12.             case MotionEvent.ACTION_DOWN:  
  13.                 postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());  
  14.                 break;  
  15.             // 取消
  16.             case MotionEvent.ACTION_CANCEL:  
  17.                 refreshDrawableState();  
  18.                 removeTapCallback();  
  19.                 break;
  20.             // 滑动
  21.             case MotionEvent.ACTION_MOVE:  
  22.                 final int x = (int) event.getX();  
  23.                 final int y = (int) event.getY();  
  24.                 int slop = mTouchSlop;  
  25.                 if ((x < 0 - slop) || (x >= getWidth() + slop) ||  
  26.                         (y < 0 - slop) || (y >= getHeight() + slop)) {  
  27.                     removeTapCallback();  
  28.                     if ((mPrivateFlags & PRESSED) != 0) {  
  29.                         removeLongPressCallback();  
  30.                         mPrivateFlags &= ~PRESSED;  
  31.                         refreshDrawableState();  
  32.                     }  
  33.                 }  
  34.                 break;  
  35.         }  
  36.         // 若该控件可点击,就一定返回true
  37.         return true;  
  38.     }  
  39.   // 若该控件不可点击,就一定返回false
  40.   return false;  
  41. }
  42.   public boolean performClick() {  
  43.       if (mOnClickListener != null) {
  44.           // 只要通过setOnClickListener()为控件View注册1个点击事件
  45.           // 那么就会给mOnClickListener变量赋值(即不为空)
  46.           // 则会往下回调onClick() & performClick()返回true
  47.           playSoundEffect(SoundEffectConstants.CLICK);  
  48.           mOnClickListener.onClick(this);  
  49.           return true;  
  50.       }  
  51.       return false;  
  52.   }  

View源码总结

        Android事件分发传递到Acitivity后,总是先传递到ViewGroup、再传递到View。流程总结如下:(假设已经经过了ViewGroup事件分发传递并传递到View)

总结

重点分析了 dispatchTouchEvent 的事件的流程机制,这一过程主要分 3 部分:

  • 1、判断是否需要拦截 —> 主要是根据 onInterceptTouchEvent 方法的返回值来决定是否拦截;

  • 2、在 DOWN 事件中将 touch 事件分发给子 View —> 这一过程如果有子 View 捕获消费了 touch 事件,会对 mFirstTouchTarget 进行赋值;

  • 3、最后一步,DOWN、MOVE、UP 事件都会根据 mFirstTouchTarget 是否为 null,决定是自己处理 touch 事件,还是再次分发给子 View。

然后介绍了整个事件分发中的几个特殊的点。

  • 1、DOWN 事件的特殊之处:事件的起点;决定后续事件由谁来消费处理;

  • 2、mFirstTouchTarget 的作用:记录捕获消费 touch 事件的 View,是一个链表结构;

  • 3、CANCEL 事件的触发场景:当父视图先不拦截,然后在 MOVE 事件中重新拦截,此时子 View 会接收到一个 CANCEL 事件。

以上就是本文的全部内容,希望对大家学习 Android 事件分发有所帮助和启发。

注:本文转载自blog.csdn.net的帅次的文章"https://shuaici.blog.csdn.net/article/details/119187090"。版权归原作者所有,此博客不拥有其著作权,亦不承担相应法律责任。如有侵权,请联系我们删除。
复制链接
复制链接
相关推荐
发表评论
登录后才能发表评论和回复 注册

/ 登录

评论记录:

未查询到任何数据!
回复评论:

分类栏目

后端 (14832) 前端 (14280) 移动开发 (3760) 编程语言 (3851) Java (3904) Python (3298) 人工智能 (10119) AIGC (2810) 大数据 (3499) 数据库 (3945) 数据结构与算法 (3757) 音视频 (2669) 云原生 (3145) 云平台 (2965) 前沿技术 (2993) 开源 (2160) 小程序 (2860) 运维 (2533) 服务器 (2698) 操作系统 (2325) 硬件开发 (2492) 嵌入式 (2955) 微软技术 (2769) 软件工程 (2056) 测试 (2865) 网络空间安全 (2948) 网络与通信 (2797) 用户体验设计 (2592) 学习和成长 (2593) 搜索 (2744) 开发工具 (7108) 游戏 (2829) HarmonyOS (2935) 区块链 (2782) 数学 (3112) 3C硬件 (2759) 资讯 (2909) Android (4709) iOS (1850) 代码人生 (3043) 阅读 (2841)

热门文章

101
推荐
关于我们 隐私政策 免责声明 联系我们
Copyright © 2020-2025 蚁人论坛 (iYenn.com) All Rights Reserved.
Scroll to Top