我的编程空间,编程开发者的网络收藏夹
学习永远不晚

Android Touch事件分发深入了解

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

Android Touch事件分发深入了解

本文带着大家深入学习触摸事件的分发,具体内容如下
1. 触摸动作及事件序列

(1)触摸事件的动作

    触摸动作一共有三种:ACTION_DOWN、ACTION_MOVE、ACTION_UP。当用户手指接触屏幕时,便产生一个动作为ACTION_DOWN的触摸事件,此时若用户的手指立即离开屏幕,会产生一个动作为ACTION_UP的触摸事件;若用户手指接触屏幕后继续滑动,当滑动距离超过了系统中预定义的距离常数,则产生一个动作为ACTION_MOVE的触摸事件,系统中预定义的用来判断用户手指在屏幕上的滑动是否是一个ACTION_MOVE动作的这个距离常量叫做TouchSlop,可通过ViewConfiguration.get(getContext()).getScaledTouchSlop()获取。

(2)事件序列

    当用户的手指接触屏幕,在屏幕上滑动,又离开屏幕,这个过程会产生一系列触摸事件:ACTION_DOWN-->若干个ACTION_MOVE-->ACTION_UP。这一系列触摸事件即为一个事件序列。 

2. 触摸事件的分发

(1)概述

    当产生了一个触摸时间后,系统要负责把这个触摸事件给一个View(TargetView)来处理,touch事件传递到TargetView的过程即为touch事件的分发。

    触摸事件的分发顺序:Activity-->顶级View-->顶级View的子View-->. . .-->Target View

    触摸事件的响应顺序:TargetView --> TargetView的父容器 --> . . . -->顶级View -->Activity

(2)toush事件分发的具体过程

  a. Activity对touch事件的分发

    当用户手指接触屏幕时,便产生了一个touch事件,封装了touch事件的MotionEvent最先被传递给当前Activity,Activity的dispatchTouchEvent方法负责touch事件的分发。分发touch事件的实际工作由当前Activity的Window完成,而Window会将touch事件传递给DecorView(当前用户界面顶级View)。Activity的dispatchTouchEvent方法代码如下:


public boolean dispatchTouchEvent(MotionEvent ev) {
  if (ev.getAction() == MotionEvent.ACTION_DOWN) {
    onUserInteraction();
  }
  if (getWindow().superDispatchTouchEvent(ev)) {
    return true;
  }
  return onTouchEvent(ev);
}

    根据以上代码可以知道,touch事件会交由Window的superDispatchTouchEvent进行分发,若这个方法返回true,意味touch事件的分发过程结束,返回false则说明经过层层分发,没有子View对这个事件进行处理,即所有子View的onTouchEvent方法都返回false(即这个touch事件没有被“消耗”)。这时会调用Activity的onTouchEvent方法来处理这个touch事件。

    在Window的superDispatchTouchEvent方法中,首先会把touch事件分发给DecorView,因为它是当前用户界面的顶级View。Window的superDispatchTouchEvent方法如下:

public abstract boolean superDispatchTouchEvent(MotionEvent ev);
    是个抽象方法,这个方法由Window的实现类PhoneWindow实现,PhoneWindow的superDispatchTouchEvent方法的代码如下:


public boolean superDispatchTouchEvent(MotionEvent ev) {
  return mDecor.superDispatchTouchEvent(event);
}

    由以上代码可得,PhoneWindow的superDispatchTouchEvent方法实际上是通过DecorView的superDispatchTouchEvent方法来完成自己的工作,也就是说,当前Activity的Window直接将这个touch事件传递给了DecorView。也就是说,目前touch事件已经经过了如下的分发:Activity-->Window-->DecorView。

b. 顶级View对touch事件的分发

    经过Activity与Window的分发,现在touch事件已经被传递到了DecorView的dispatchTouchEvent方法中。DecorView本质上是一个ViewGroup(更具体的说是FrameLayout),ViewGroup的dispatchTouchEvent方法所做的工作可以分为如下几个阶段,第一个阶段的主要代码如下:


//Handle an initial down.
if (actionMasked == MotionEvent.ACTION_DOWN) {
  //Throw away all previous state when starting a new touch gesture.
  //The framework may have dropped the up or cancel event for the previous gesture due to an app switch, ANR, or some other state change.
  cancelAndClearTouchTargets(ev);
  resetTouchState();
}

    第一阶段的主要工作有俩:一是在第6行的resetTouchState方法中完成了对FLAG_DISALLOW_INTERCEPT标记的重置;二是第5行的cancelAndClearTouchTargets方法会清除当前MotionEvent的touch target。关于FLAG_DISALLOW_INTERCEPT标记和touch target,在下文会有相关说明。

    第二阶段的主要工作是决定当前ViewGroup是否拦截本次的touch事件,主要代码如下:


//Check for interception.
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWM || mFirstTouchTarget != null) {
  final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
  if (!disallowIntercept) {
    intercepted = onInterceptTouchEvent(ev);
    ev.setAction(action); //restore action in case it was changed
  } else {
    intercepted = false;
  }
} else {
  //There are no touch targets and this action is not an initial down so this view group continues to intercept touches.
  intercept =true;
}

    由以上代码我们可以知道,当一个touch事件被传递到ViewGroup时,会先判断这个touch事件的动作是否是ACTION_DOWN,如果这个事件是ACTION_DOWN或者mFirstTouchTarget不为null,就会根据FLAG_DISALLOW_INTERCEPT标记决定是否拦截这个touch事件。那么mFirstTouchTarget是什么呢?当touch事件被ViewGroup的子View成功处理时,mFirstTouchTarget就会被赋值为成功处理touch事件的View,也就是上面提高的touch target。

    总结一下上述代码的流程:在子View不干预ViewGroup的拦截的情况下(上述代码中的disallowIntercept为false),若当前事件为ACTION_DOWN或者mFirstTouchTarget不为空,则会调用ViewGroup的onInterceptTouchEvent方法来决定最终是否拦截此事件;否则(没有TargetView并且此事件不是ACTION_DOWN),当前ViewGroup就拦截下此事件。 一旦ViewGroup拦截了某次touch事件,那么mFirstTouchTarget就不会被赋值,因此当再有ACTION_MOVE或是ACTION_UP传递到该ViewGroup时,mTouchTarget就为null,所以上述代码第3行的条件就为false,ViewGroup会拦截下来。由此可得到的结论是:一旦ViewGroup拦截了某次事件,则同一事件序列中的剩余事件也会它默认被拦截而不会再询问是否拦截(即不会再调用onInterceptTouchEvent)。

    这里存在一种特殊情形,就是子View通过requestDisallowInterceptTouchEvent方法设置父容器的FLAG_DISALLOW_INTERCEPT为true,这个标记指示是否不允许父容器拦截,为true表示不允许。这样做能够禁止父容器拦截除ACTION_DOWN以外的所有touch事件。之所以不能够拦截ACTION_DOWN事件,是因为每当ACTION_DOWN事件到来时,都会重置FLAG_DISALLOW_INTERCEPT这个标记位为默认值(false),所以每当开始一个新touch事件序列(即到来一个ACTION_DOWN动作),都会通过调用onInterceptTouchEven询问ViewGroup是否拦截此事件。当ACTION_DOWN事件到来时,重置标记位的工作是在上面的第一阶段完成的。  

    接下来,会进入第三阶段的工作:


final boolean canceled = resetCancelNextUpFlag(this) || actionMasked == MotionEvent.ACTION_CANCEL;
final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
TouchTarget newTouchTarget = null;
boolean alreadyDispatchedToNewTouchTarget = false;
if (!canceled && !intercepted) {
  // 不是ACTION_CANCEL并且不拦截
  if (actionMasked == MotionEvent.ACTION_DOWN) {
     // 若当前事件为ACTION_DOWN则去寻找这次事件新出现的touch target
     final int actionIndex = ev.getActionIndex(); // always 0 for down
     ...
     final int childrenCount = mChildrenCount;
     if (newTouchTarget == null && childrenCount != 0) {
       // 根据触摸的坐标寻找能够接收这个事件的touch target
       final float x = ev.getX(actionIndex);
       final float y = ev.getY(actionIndex);
       final View[] children = mChildren;
       // 遍历所有子View
       for (int i = childrenCount - 1; i >= 0; i--) {
         final int childIndex = i;
         final View child = children[childIndex];
         // 寻找可接收这个事件并且touch事件坐标在其区域内的子View
         if (!canViewReceivePointerEvents(child) || !isTransformedTouchPointInView(x, y, child, null)) {
           continue;
         }
         newTouchTarget = getTouchTarget(child); // 找到了符合条件的子View,赋值给newTouchTarget
         if (newTouchTarget != null) {
           //Child is already receiving touch within its bounds.
           //Give it the new pointer in addition to ones it is handling.
           newTouchTarget.pointerIdBits |= idBitsToAssign;
           break;
         }
         resetCancelNextUpFlag(child);
         // 把ACTION_DOWN事件传递给子组件进行处理
         if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
           //Child wants to receive touch within its bounds.
           mLastTouchDownTime = ev.getDownTime();
           if (preorderedList != null) {
             //childIndex points into presorted list, find original index
             for (int j=0;j<childrenCount;j++) {
               if (children[childIndex]==mChildren[j]) {
                 mLastTouchDownIndex=j;
                 break;
               }
             }
           } else {
             mLastTouchDownIndex = childIndex;
           }
           mLastTouchDownX = ev.getX();
           mLastTouchDownY = ev.getY();
           //把mFirstTouchTarget赋值为newTouchTarget,此子View成为新的touch事件的起点
           newTouchTarget = addTouchTarget(child, idBitsToAssign);
           alreadyDispatchedToNewTouchTarget = true;
           break;
         }           
       }
     }
  }
}

    当ViewGroup不拦截本次事件,则touch事件会分发给它的子View进行处理,相关代码从第21行开始:遍历所有ViewGroup的子View,寻找能够处理此touch事件的子View,若一个子View不在播放动画并且touch事件坐标位于其区域内,则该子View能够处理此touch事件,并且会把该子View赋值给newTouchTarget。

    若当前遍历到的子View能够处理此touch事件,就会进入第38行的dispatchTransformedTouchEvent方法,该方法实际上调用了子View的dispatchTouchEvent方法。dispatchTransformedTouchEvent方法中相关的代码如下:


if (child == null) {
  handled = super.dispatchTouchEvent(event);
} else {
  handled = child.dispatchTouchEvent(event);
}

    若dispatchTransformedTouchEvent方法传入的child参数不为null,则会调用child(即处理touch事件的子View)的dispatchTouchEvent方法。若该子View的dispatchTouchEvent方法返回true,则dispatchTransformedTouchEvent方法也会返回true,则表示成功找到了一个处理该事件的touch target,会在第55行把newTouchTarget赋值给mFirstTouchTarget(这一赋值过程是在addTouchTarget方法内部完成的),并跳出对子View遍历的循环。若子View的dispatchTouchEvent方法返回false,ViewGroup就会把事件分发给下一个子View。

    若遍历了所有子View后,touch事件都没被处理(该ViewGroup没有子View或是所有子View的dispatchTouchEvent返回false),ViewGroup会自己处理touch事件,相关代码如下:


 if (mFirstTouchTarget == null) {
   handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS);
 }

    由以上代码可知,ViewGroup自己处理touch事件时,会调用dispatchTransformedTouchEvent方法,传入的child参数为null。根据上文的分析,传入的chid为null时,会调用super.dispatchTouchEvent方法,即调用View类的dispatchTouchEvent方法。 

c. View对touch事件的处理

    View的dispatchTouchEvent方法的主要代码如下:


public boolean dispatchTouchEvent(MotionEvent event) {
  boolean result = false;
  . . .
  if (onFilterTouchEventForSecurity(event)) {
    //noinspection SimplifiableIfStatement
    ListenerInfo li = mListenerInfo;
    if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
        && li.mOnTouchListener.onTouch(this, event)) {
      result = true;
    }
    if (!result && onTouchEvent(event)) {
      result = true;
    }
    . . .
    return result;
}

    由上述代码可知,View对touch事件的处理过程如下:由于View不包含子元素,所以它只能自己处理事件。它首先会判断是否设置了OnTouchListener,若设置了,会调用onTouch方法,若onTouch方法返回true(表示该touch事件已经被消耗),则不会再调用onTouchEvent方法;若onTouch方法返回false或没有设置OnTouchListener,则会调用onTouchEvent方法,onTouchEvent对touch事件进行具体处理的相关代码如下:


if (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
  switch (event.getAction()) {
    case MotionEvent.ACTION_UP:
      boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
      if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
        . . .
        if (!mHasPerformedLongPress) {
          //This is a tap, so remove the longpress check 
          removeLongPressCallback();
          //Only perform take click actions if we were in the pressed state
          if (!focusTaken) {
            //Use a Runnable and post this rather than calling performClick directly.
            //This lets other visual state of the view update before click actions start.
            if (mPerformClick == null) {
              mPerformClck = new PeformClick();
            }
            if (!post(mPerformClick)) {
              performClick();
            }
          }
        }
        . . .
      }
      break;
  }
  . . .
  return true;
}


    由以上代码可知,只要View的CLICKABLE属性和LONG_CLICKABLE属性有一个为true(View的CLICKABLE属性和具体View有关,LONG_CLICKABLE属性默认为false,setOnClikListener和setOnLongClickListener会分别自动将以上俩属性设为true),那么这个View就会消耗这个touch事件,即使这个View处于DISABLED状态。若当前事件是ACTION_UP,还会调用performClick方法,该View若设置了OnClickListener,则performClick方法会在其内部调用onClick方法。performClick方法代码如下:


public boolean performClick() {
  final boolean result;
  final ListenerInfo li = mListenerInfo;
  if (li != null && li.mOnClickListener != null) {
    playSoundEffect(SoundEffectConstants.CLICK);
    li.mOnClickListener.onClick(this);
    result = true;
  } else {
    result = false;
  }
  sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
  return result;
}


 

以上是我学习Android中触摸事件分发后的简单总结,很多地方叙述的还不够清晰准确

您可能感兴趣的文章:Android事件分发机制的详解Android View事件分发和消费源码简单理解Android View的事件分发机制谈谈对Android View事件分发机制的理解Android事件分发机制(下) View的事件处理Android事件分发机制(上) ViewGroup的事件分发Android View事件分发机制详解Android View 事件分发机制详解Android 事件分发详解及示例代码android事件分发机制的实现原理


免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

Android Touch事件分发深入了解

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

Android Touch事件分发深入了解

本文带着大家深入学习触摸事件的分发,具体内容如下 1. 触摸动作及事件序列 (1)触摸事件的动作 触摸动作一共有三种:ACTION_DOWN、ACTION_MOVE、ACTION_UP。当用户手指接触屏幕时,便产生一个动作为ACTI
2022-06-06

Android Touch事件分发过程详解

本文以实例形式讲述了Android Touch事件分发过程,对于深入理解与掌握Android程序设计有很大的帮助作用。具体分析如下: 首先,从一个简单示例入手: 先看一个示例如下图所示:布局文件 :
2022-06-06

带你深入了解Android的事件分发机制

Android的事件分发机制是指在Android系统中,如何将用户的触摸事件、按键事件等传递给正确的View进行处理的一套机制。它是Android应用程序中实现交互的重要部分,确保用户的操作能够被正确地捕获和处理。 Android的事件分发
2023-08-18

30分钟搞清楚Android Touch事件分发机制

Touch事件分发中只有两个主角:ViewGroup和View。Activity的Touch事件事实上是调用它内部的ViewGroup的Touch事件,可以直接当成ViewGroup处理。 View在ViewGroup内,ViewGroup
2022-06-06

深入理解 Android事件分发机制源码(基于9.0)

touch事件最开始从Activity 的 dispatchTouchEvent() 方法开始的/frameworks/base/core/java/android/app/Activity.java /*** Called to proc
2022-06-06

AndroidView的事件分发机制深入分析讲解

事件分发从手指触摸屏幕开始,即产生了触摸信息,被底层系统捕获后会传递给Android的输入系统服务IMS,通过Binder把消息发送到activity,activity会通过phoneWindow、DecorView最终发送给ViewGroup。这里就直接分析ViewGroup的事件分发
2023-01-29

Android事件分发机制深入刨析原理及源码

Android 的事件分发机制大体可以分为三部分:事件生产、事件分发 、事件消费。事件的生产是由用户点击屏幕产生,我们这次着重分析事件的分发和消费,因为事件分发和处理联系的过于紧密,这篇文章将把事件的分发和消费放在一起分析
2023-05-16

深入浅析Android项目中的 View事件分发机制

本篇文章为大家展示了深入浅析Android项目中的 View事件分发机制,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。具体方法如下:public class MyButton extends But
2023-05-31

Android 事件触发机制的深入学习

Android 事件触发机制的深入学习最近在研究android的事件触发和传播机制,说来很惭愧,web下的事件太熟悉不过了,可在android中却很郁闷,常用的触摸事件都糊里糊涂的,在网上看了半天,也整理一份,供大家参考:public c
2023-05-31

深入解析Android中的事件传递

前言 前段时间工作中遇到了一个问题,即在软键盘弹出后想监听back事件,但是在Activity中重写了对应的onKeyDown函数却怎么也监听不到,经过一阵Google之后才发现需要重写View的dispatchKeyEventPreIme
2022-06-06

深入了解Node事件循环(EventLoop)机制

主线程从"任务队列"中读取事件,这个过程是循环不断的,所以整个的这种运行机制又称为Event Loop(事件循环)。下面本篇文章就来带大家掌握Node.js中的eventloop,希望对大家有所帮助!
2023-05-14

Node.js 事件循环:深入了解引擎室

Node.js 事件循环是一种强大的机制,它始终监听事件并异步执行回调函数。了解它的工作原理至关重要,因为它决定了 Node.js 应用程序的性能和响应能力。
Node.js 事件循环:深入了解引擎室
2024-03-06

Android 事件分发机制 讲解

1、分发事件的组件 分发事件的组件,也称为分发事件者,包括Activity、ViewGroup和View。它们三者的一般结构为:从上图中可以看出,Activity包括了ViewGroup,ViewGroup又可以包含多个View。 2、分发
2022-06-06

Node.js Serverless 中的事件处理:深入了解 EventEmitter

本文将深入探讨 Node.js Serverless 中的事件处理,重点介绍 EventEmitter 的使用及其优势,并通过演示代码展示如何利用 EventEmitter 实现事件处理。
Node.js Serverless 中的事件处理:深入了解 EventEmitter
2024-02-21

Android事件的分发机制详解

在分析Android事件分发机制前,明确android的两大基础控件类型:View和ViewGroup。View即普通的控件,没有子布局的,如Button、TextView. ViewGroup继承自View,表示可以有子控件,如Linea
2022-06-06

编程热搜

  • Android:VolumeShaper
    VolumeShaper(支持版本改一下,minsdkversion:26,android8.0(api26)进一步学习对声音的编辑,可以让音频的声音有变化的播放 VolumeShaper.Configuration的三个参数 durati
    Android:VolumeShaper
  • Android崩溃异常捕获方法
    开发中最让人头疼的是应用突然爆炸,然后跳回到桌面。而且我们常常不知道这种状况会何时出现,在应用调试阶段还好,还可以通过调试工具的日志查看错误出现在哪里。但平时使用的时候给你闹崩溃,那你就欲哭无泪了。 那么今天主要讲一下如何去捕捉系统出现的U
    Android崩溃异常捕获方法
  • android开发教程之获取power_profile.xml文件的方法(android运行时能耗值)
    系统的设置–>电池–>使用情况中,统计的能耗的使用情况也是以power_profile.xml的value作为基础参数的1、我的手机中power_profile.xml的内容: HTC t328w代码如下:
    android开发教程之获取power_profile.xml文件的方法(android运行时能耗值)
  • Android SQLite数据库基本操作方法
    程序的最主要的功能在于对数据进行操作,通过对数据进行操作来实现某个功能。而数据库就是很重要的一个方面的,Android中内置了小巧轻便,功能却很强的一个数据库–SQLite数据库。那么就来看一下在Android程序中怎么去操作SQLite数
    Android SQLite数据库基本操作方法
  • ubuntu21.04怎么创建桌面快捷图标?ubuntu软件放到桌面的技巧
    工作的时候为了方便直接打开编辑文件,一些常用的软件或者文件我们会放在桌面,但是在ubuntu20.04下直接直接拖拽文件到桌面根本没有效果,在进入桌面后发现软件列表中的软件只能收藏到面板,无法复制到桌面使用,不知道为什么会这样,似乎并不是很
    ubuntu21.04怎么创建桌面快捷图标?ubuntu软件放到桌面的技巧
  • android获取当前手机号示例程序
    代码如下: public String getLocalNumber() { TelephonyManager tManager =
    android获取当前手机号示例程序
  • Android音视频开发(三)TextureView
    简介 TextureView与SurfaceView类似,可用于显示视频或OpenGL场景。 与SurfaceView的区别 SurfaceView不能使用变换和缩放等操作,不能叠加(Overlay)两个SurfaceView。 Textu
    Android音视频开发(三)TextureView
  • android获取屏幕高度和宽度的实现方法
    本文实例讲述了android获取屏幕高度和宽度的实现方法。分享给大家供大家参考。具体分析如下: 我们需要获取Android手机或Pad的屏幕的物理尺寸,以便于界面的设计或是其他功能的实现。下面就介绍讲一讲如何获取屏幕的物理尺寸 下面的代码即
    android获取屏幕高度和宽度的实现方法
  • Android自定义popupwindow实例代码
    先来看看效果图:一、布局
  • Android第一次实验
    一、实验原理 1.1实验目标 编程实现用户名与密码的存储与调用。 1.2实验要求 设计用户登录界面、登录成功界面、用户注册界面,用户注册时,将其用户名、密码保存到SharedPreference中,登录时输入用户名、密码,读取SharedP
    Android第一次实验

目录