首页 最新 热门 推荐

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

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

  • 25-02-16 05:21
  • 4576
  • 5396
blog.csdn.net

前言

        每次听说事件分发就觉得好难、肯定不好学,其实事件分发也就那么回事。将本文看下去不说你能对事件分发有多么了解、多么深入,但是在应用开发中简单的使用是绝对没有问题的,下面咱就不叨叨,开始咱们的学习。

        Android Touch事件的分发是 Android 工程师必备技能之一。关于事件分发主要有几个方向可以展开深入分析:

  • 1.Touch事件是如何从驱动层传递给 Framework 层的 InputManagerService;

  • 2.WMS是如何通过 ViewRootImpl 将事件传递到目标窗口;

  • 3.Touch事件到达 DecorView 后,是如何一步步传递到内部的子 View 中的。

名词了解

  • AMS:统一调度所有应用程序的Activity

  • WMS:控制所有Window的显示与隐藏以及要显示的位置

什么是事件

        事件:Touch,既触碰,就是用户触碰手机界面的交互事件。事件主要包括:按下、滑动、抬起与取消。这些事件被封装成MotionEvent对象。该对象中的主要事件如下表所示:

事件流

        按下、滑动、抬起、取消这几种事件组成了一个事件流。事件流以按下为开始,中间可能有若干次滑动,以抬起或取消作为结束。

什么是事件分发

        事件分发:就是将一次完整的点击所包含的点击事件传递到某个具体的View或ViewGroup,让该View或该ViewGroup处理(消费)它。分发是「从上往下依次传递」的,其中可能经过的对象有「最上层Activity」,「中间层ViewGroup」,「最下层View」。例如在屏幕中有一个Button控件,点击它时该次点击事件就会从Activity传到Button所在的ViewGroup,最后传到该Button控件去处理它。即事件分发就是从上往下依次遍历,直到找到能够处理消费这次点击事件的View或ViewGroup。

        在Android对事件分发的处理过程中,主要是对「Down事件作分发」,进而找到能够处理Down事件的组件。对于事件流中后续的事件(如Move、Up等),则直接分发给能够处理按下事件的组件。故本文讨论的内容则是主要针对按下事件的。

思路梳理

在深入分析事件分发源码之前,需要先弄清楚2个概念。

ViewGroup

        ViewGroup 是一组 View 的组合,在其内部有可能包含多个子 View,当手指触摸屏幕上时,手指所在的区域既能在ViewGroup显示范围内,也可能在其内部 View 控件上。因此它内部的事件分发的重心是处理当前 ViewGroup 和子 View 之间的逻辑关系:

  • 1.当前 ViewGroup 是否需要拦截Touch事件;

  • 2.是否需要将Touch事件继续分发给子 View;

  • 3.如何将Touch事件分发给子 View。

View

        View 是一个单纯的控件,不能再被细分,内部也并不会存在子 View,所以它的事件分发的重点在于当前 View 如何去处理 Touch 事件,并根据相应的手势逻辑进行一些列的效果展示(比如滑动,放大,点击,长按等)。

  • 1.是否存在 TouchListener;

  • 2.是否自己接收处理 touch 事件(主要逻辑在 onTouchEvent 方法中)。

涉及事件分发的方法

方法的简单用途解析

我们可以发现这三个方法的返回值都为boolean类型,其实它们就是通过返回值来决定下一步的传递处理方向。

1、dispatchTouchEvent() ——用来分发事件所用

该方法会将Touch事件「自上而下」依次分发到子元素中,直到被终止或者到达View层,该方法也是采用一种「隧道方式来分发」。在其中会调用onInterceptTouchEvent()和onTouchEvent(),「一般不会重写」。

  • 返回false则不拦截继续往下分发;

  • 返回true则拦截住该事件不在向下层元素分发;

在dispatchTouchEvent()方法中默认返回false。

2、onInterceptTouchEvent() ——用来拦截事件所用

  • 返回false不拦截事件,Touch事件就会往下传递给其子View。

  • 返回true,该事件将会被拦截,并且被当前ViewGroup处理,调用ViewGroup的onTouchEvent()方法。

3、onTouchEvent() ——用来处理事件

  • 返回true则表示该View能处理该事件,事件将终止向上传递(传递给其父View)。

  • 返回false表示不能处理,则把事件传递给其父View的onTouchEvent()方法来处理。

拥有上述方法的类

「注意:」 需要特别注意一点就是ViewGroup中额外拥有onInterceptTouchEvent()方法,其他两个方法为这三种类所共同拥有。

事件分发流程

单个事件触发后,事件分发流程:Activity①>ViewGroup②>View③,如下图:

即要想充分理解Android分发机制,本质上是要理解:

  • Activity对点击事件的分发机制

  • ViewGroup对点击事件的分发机制

  • View对点击事件的分发机制

        从U型图中可以发现,由父组件不断向子组件分发,若子组件能够处理,则立刻返回。若子组件都不处理,那传递到底层的子组件,再返回回来。「整个View之间的事件分发,实质上就是一个大的递归函数」。

实例

        下面咱们写一个简单实例来更好的理解这个大U型图,效果图:

创建实例

创建MyViewGroup继承ViewGroup

重写dispatchTouchEvent()、onInterceptTouchEvent()、onTouchEvent()方法

  1. public class MyViewGroup extends RelativeLayout {
  2.     public MyViewGroup(Context context) {
  3.         super(context);
  4.     }
  5.     public MyViewGroup(Context context, AttributeSet attrs) {
  6.         super(context, attrs);
  7.     }
  8.     @Override
  9.     public boolean dispatchTouchEvent(MotionEvent ev) {
  10.         MLog.logEvent("MyViewGroup.dispatchTouchEvent:",ev);
  11.         return super.dispatchTouchEvent(ev);
  12.     }
  13.     @Override
  14.     public boolean onInterceptTouchEvent(MotionEvent ev) {
  15.         MLog.logEvent("MyViewGroup.onInterceptTouchEvent:",ev);
  16.         return false;
  17.     }
  18.     @Override
  19.     public boolean onTouchEvent(MotionEvent event) {
  20.         MLog.logEvent("MyViewGroup.onTouchEvent自行处理:",event);
  21.         return super.onTouchEvent(event);
  22.     }
  23. }

创建MyView继承View

重写dispatchTouchEvent()、onTouchEvent()方法

  1. public class MyView extends View {
  2.     public MyView(Context context) {
  3.         super(context);
  4.     }
  5.     public MyView(Context context, @Nullable AttributeSet attrs) {
  6.         super(context, attrs);
  7.     }
  8.     @Override
  9.     public boolean dispatchTouchEvent(MotionEvent ev) {
  10.         MLog.logEvent("MyView.dispatchTouchEvent:",ev);
  11.         return super.dispatchTouchEvent(ev);
  12.     }
  13.     @Override
  14.     public boolean onTouchEvent(MotionEvent event) {
  15.         MLog.logEvent("MyView.onTouchEvent:",event);
  16.         return super.onTouchEvent(event);
  17.     }
  18. }

创建TouchActivity继承Activity

重写dispatchTouchEvent()、onTouchEvent()方法

  1. public class TouchActivity extends Activity {
  2.     @Override
  3.     protected void onCreate(Bundle savedInstanceState) {
  4.         super.onCreate(savedInstanceState);
  5.         setContentView(R.layout.activity_touch);
  6.     }
  7.     @Override
  8.     public boolean dispatchTouchEvent(MotionEvent ev) {
  9.         MLog.logEvent("TouchActivity.dispatchTouchEvent:",ev);
  10.         return super.dispatchTouchEvent(ev);
  11.     }
  12.     @Override
  13.     public boolean onTouchEvent(MotionEvent event) {
  14.         MLog.logEvent("TouchActivity.onTouchEvent:",event);
  15.         return super.onTouchEvent(event);
  16.     }
  17. }

创建布局文件

添加控件MyViewGroup和MyView

  1. "1.0" encoding="utf-8"?>
  2. <com.scc.demo.view.MyViewGroup xmlns:android="http://schemas.android.com/apk/res/android"
  3.     android:id="@+id/mtrg_touch"
  4.     android:layout_width="match_parent"
  5.     android:layout_height="300dp"
  6.     android:background="@color/color_FF773D">
  7.     <com.scc.demo.view.MyView
  8.         android:id="@+id/mtv_onclick"
  9.         android:layout_width="120dp"
  10.         android:layout_height="60dp"
  11.         android:layout_centerHorizontal="true"
  12.         android:layout_marginTop="40dp"
  13.         android:background="@color/color_188FFF"/>
  14. com.scc.demo.view.MyViewGroup>

MLog.logEvent()

方便查看对应事件

  1. public static void logEvent(String msg, MotionEvent event) {
  2.         String motionEvent = "";
  3.         switch (event.getAction()){
  4.             case MotionEvent.ACTION_DOWN://在屏幕按下时(所有事件的开始)
  5.                 motionEvent="DOWN";
  6.                 break;
  7.             case MotionEvent.ACTION_UP://在屏幕抬起时(与DOWN对应)
  8.                 motionEvent="UP";
  9.                 break;
  10.             case MotionEvent.ACTION_MOVE://在屏幕上滑动时
  11.                 motionEvent="MOVE";
  12.                 break;
  13.             case MotionEvent.ACTION_CANCEL://滑动超出控件边界时
  14.                 motionEvent="CANCEL";
  15.                 break;
  16.         }
  17.         Log.e("SccEvent", msg + motionEvent);
  18.     }

点击页面,看效果

点击Activity(白色区域)

运行结果

  1. E/SccEvent: TouchActivity.dispatchTouchEvent:DOWN
  2. E/SccEvent: TouchActivity.onTouchEvent:DOWN
  3. E/SccEvent: TouchActivity.dispatchTouchEvent:MOVE
  4. E/SccEvent: TouchActivity.onTouchEvent:MOVE
  5. E/SccEvent: TouchActivity.dispatchTouchEvent:MOVE
  6. E/SccEvent: TouchActivity.onTouchEvent:MOVE
  7. E/SccEvent: TouchActivity.dispatchTouchEvent:UP
  8. E/SccEvent: TouchActivity.onTouchEvent:UP

点击ViewGroup(黄色区域)

运行结果

  1. E/SccEvent: TouchActivity.dispatchTouchEvent:DOWN
  2. E/SccEvent: MyViewGroup.dispatchTouchEvent:DOWN
  3. E/SccEvent: MyViewGroup.onInterceptTouchEvent:DOWN
  4. E/SccEvent: MyViewGroup.onTouchEvent:DOWN
  5. E/SccEvent: TouchActivity.onTouchEvent:DOWN
  6. E/SccEvent: TouchActivity.dispatchTouchEvent:MOVE
  7. E/SccEvent: TouchActivity.onTouchEvent:MOVE
  8. E/SccEvent: TouchActivity.dispatchTouchEvent:MOVE
  9. E/SccEvent: TouchActivity.onTouchEvent:MOVE
  10. E/SccEvent: TouchActivity.dispatchTouchEvent:UP
  11. E/SccEvent: TouchActivity.onTouchEvent:UP

点击View(蓝色区域)

运行结果

  1. E/SccEvent: TouchActivity.dispatchTouchEvent:DOWN
  2. E/SccEvent: MyViewGroup.dispatchTouchEvent:DOWN
  3. E/SccEvent: MyViewGroup.onInterceptTouchEvent:DOWN
  4. E/SccEvent: MyView.dispatchTouchEvent:DOWN
  5. E/SccEvent: MyView.onTouchEvent:DOWN
  6. E/SccEvent: MyViewGroup.onTouchEvent:DOWN
  7. E/SccEvent: TouchActivity.onTouchEvent:DOWN
  8. E/SccEvent: TouchActivity.dispatchTouchEvent:MOVE
  9. E/SccEvent: TouchActivity.onTouchEvent:MOVE
  10. E/SccEvent: TouchActivity.dispatchTouchEvent:MOVE
  11. E/SccEvent: TouchActivity.onTouchEvent:MOVE
  12. E/SccEvent: TouchActivity.dispatchTouchEvent:UP
  13. E/SccEvent: TouchActivity.onTouchEvent:UP

结果分析

        重叠区域越多触发事件越多。这就用到了事件分发。点击蓝色区域,走完了一个大U分发,没有人拦截和处理。

        看看上面的日志你会发现按下事件走了那么多方法,为什么滑动和抬起仅调用Activity中的方法?

        原因就是:MOVE、UP 等事件的分发交给谁,取决于它们的起始事件 Down 是由谁捕获的。可参阅文章后面的「为什么 DOWN 事件特殊」

        本次将所有Touch事件打印,后续仅打印DOWN事件。

事件分发和处理

Activity处理和分发

Activity处理

1.将MyViewGroup.dispatchTouchEvent()返回修改为false

  1.   @Override
  2.     public boolean dispatchTouchEvent(MotionEvent ev) {
  3.         MLog.logEvent("MyViewGroup.dispatchTouchEvent:",ev);
  4.         return false;
  5.     }

运行结果

  1. E/SccEvent: TouchActivity.dispatchTouchEvent:DOWN
  2. E/SccEvent: MyViewGroup.dispatchTouchEvent:DOWN
  3. E/SccEvent: TouchActivity.onTouchEvent:DOWN
  4. E/SccEvent: TouchActivity.SccEvent就在这里玩

2.MyViewGroup仅拦截不处理,也会交给Activity.onTouchEvent处理

3.向下分发的事件ViewGroup和View都不处理,最后还是交给Activity.onTouchEvent处理(如「上面点击蓝色区域结果分析」)

Activity分发

        Actvitiy是事件分发的顶层,dispatchTouchEvent方法返回true不做分发,事件结束。返回false或者super.dispatchTouchEvent(ev)则向下分发事件。向下分发就没必要处理(重写)dispatchTouchEvent()

ViewGroup拦截处理和分发

ViewGroup拦截处理

        确保Activity做好分发后(即不对dispatchTouchEvent做修改),「修改MyViewGroup.onInterceptTouchEvent()和MyViewGroup.onTouchEvent()方法」,ViewGroup的重点在于事件拦截(onInterceptTouchEvent),所以咱重写拦截事件和处理事件的两个方法即可。

  1.     @Override
  2.     public boolean dispatchTouchEvent(MotionEvent ev) {
  3.         MLog.logEvent("MyViewGroup.dispatchTouchEvent:",ev);
  4.         return super.dispatchTouchEvent(ev);
  5.     }
  6.     @Override
  7.     public boolean onInterceptTouchEvent(MotionEvent ev) {
  8.         MLog.logEvent("MyViewGroup.onInterceptTouchEvent:",ev);
  9.         return true;
  10.     }
  11.     @Override
  12.     public boolean onTouchEvent(MotionEvent event) {
  13.         MLog.logEvent("MyViewGroup.onTouchEvent:",event);
  14.         MLog.logEvent("MyViewGroup.SccEvent就在这里玩",event.getAction());
  15.         return true;
  16.     }

运行结果

  1. E/SccEvent: TouchActivity.dispatchTouchEvent:DOWN
  2. E/SccEvent: MyViewGroup.dispatchTouchEvent:DOWN
  3. E/SccEvent: MyViewGroup.onInterceptTouchEvent:DOWN
  4. E/SccEvent: MyViewGroup.onTouchEvent:DOWN
  5. E/SccEvent: MyViewGroup.SccEvent就在这里玩

结果分析

        在MyViewGroup.onTouchEvent()方法将事件处理了,MyViewGroup告诉TouchActivity已经处理掉了。这个时候MyView想不想处理都不重要了,因为事件已经让MyViewGroup拐跑了。

 如果这个时候返回 super.onTouchEvent(event),则默认未处理,后面会交给TouchActivity.onTouchEvent()来处理。看效果

  1.     @Override
  2.     public boolean onTouchEvent(MotionEvent event) {
  3.         MLog.logEvent("MyViewGroup.onTouchEvent:",event);
  4.         MLog.logEvent("MyViewGroup.SccEvent就在这里玩",event.getAction());
  5.         return super.onTouchEvent(event);
  6.     }

运行结果

  1. E/SccEvent: TouchActivity.dispatchTouchEvent:DOWN
  2. E/SccEvent: MyViewGroup.dispatchTouchEvent:DOWN
  3. E/SccEvent: MyViewGroup.onInterceptTouchEvent:DOWN
  4. E/SccEvent: MyViewGroup.onTouchEvent:DOWN
  5. E/SccEvent: MyViewGroup.SccEvent就在这里玩
  6. TouchActivity.onTouchEvent:DOWN

那这就是个「失败的拦截和处理」。

ViewGroup分发

不对ViewGroup的dispatchTouchEvent()和onInterceptTouchEvent()做修改即可

View处理和分发

View处理

确保ViewGroup做好分发后,「修改MyView.onTouchEvent()方法」。

  1. @Override
  2.     public boolean dispatchTouchEvent(MotionEvent ev) {
  3.         MLog.logEvent("MyView.dispatchTouchEvent:",ev);
  4.         return super.dispatchTouchEvent(ev);
  5.     }
  6.     @Override
  7.     public boolean onTouchEvent(MotionEvent event) {
  8.         MLog.logEvent("MyView.onTouchEvent:",event);
  9.         MLog.logEvent("MyView.SccEvent就在这里玩",event.getAction());
  10.         return true;
  11. //        return super.onTouchEvent(event);
  12.     }

运行结果

  1. E/SccEvent: TouchActivity.dispatchTouchEvent:DOWN
  2. E/SccEvent: MyViewGroup.dispatchTouchEvent:DOWN
  3. E/SccEvent: MyViewGroup.onInterceptTouchEvent:DOWN
  4. E/SccEvent: MyView.dispatchTouchEvent:DOWN
  5. E/SccEvent: MyView.onTouchEvent:DOWN
  6. E/SccEvent: MyView.SccEvent就在这里玩

结果分析

        在MyView.onTouchEvent()将事件处理了,MyTouchView告诉MyViewGroup我消费掉了,MyViewGroup告诉TouchActivity消费掉了。

View分发

        View本身就是最小的View,没有子View给他做分发,所以「MyView.dispatchTouchEvent()」 方法可以不做改变,或者不重写。

onTouch()与onTouchEvent()的区别(View)

onTouchEvent()方法

        onTouchEvent是手机屏幕事件的处理方法,是获取的对屏幕的各种操作,比如向左向右滑动,点击返回按钮等等。当屏幕有touch事件时,此方法就会别调用。

onTouch方法

        onTouch()是OnTouchListener接口的方法,它是获取某一个控件的触摸事件,因此使用时,必须使用setOnTouchListener绑定到控件,然后才能鉴定该控件的触摸事件。

小结

        onTouch()优先级比onTouchEvent()高,会先触发。

        假如onTouch方法返回false,会接着触发onTouchEvent,反之onTouchEvent方法不会被调用。

        onClick等事件的实现等等都基于onTouchEvent,假如onTouchEvent返回true,这些事件将不会被触发。

以上以ViewGroup分发为前提。

上面几种情况基本包含应用中出现的场景,合理利用你也可以正确的使用Touch事件分发。是不是很简单。

  • 向上滑动:查看「方法的简单用途解析」进行巩固

  • 向下滑动:查看「源码分析」进一步学习

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

本来是想一篇文章搞定,后来发现文章过长,会让人感到烦躁,所以拆分两篇。

为什么 DOWN 事件特殊

        所有 「Touch」 事件都是从 「DOWN」 事件开始的,这是 DOWN 事件比较特殊的原因之一。另一个原因是 「DOWN」 事件的处理结果会直接影响后续 「MOVE、UP」 事件的逻辑。也就是说「后续的 MOVE、UP 等事件的分发交给谁,取决于它们的起始事件 Down 是由谁捕获的。」

总结

重点分析了 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/118526017"。版权归原作者所有,此博客不拥有其著作权,亦不承担相应法律责任。如有侵权,请联系我们删除。
复制链接
复制链接
相关推荐
发表评论
登录后才能发表评论和回复 注册

/ 登录

评论记录:

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

分类栏目

后端 (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