(4.1.40.4)Android控件事件MotionEvent详解

简要介绍触摸事件,主要包括 单点触控、多点触控、鼠标事件 以及 getAction() 和 getActionMasked() 的区别

一、单点触控

事件简介
ACTION_DOWN手指 初次接触到屏幕 时触发。
ACTION_MOVE手指 在屏幕上滑动 时触发,会多次触发。
ACTION_UP手指 离开屏幕 时触发。
ACTION_CANCEL事件 被上层拦截 时触发。
ACTION_OUTSIDE手指 不在控件区域 时触发。

涉及到以下方法:

getAction() //获取事件类型。
getX()  //获得触摸点在当前 View 的 X 轴坐标。
getY()  //获得触摸点在当前 View 的 Y 轴坐标。
getRawX()   //获得触摸点在整个屏幕的 X 轴坐标。
getRawY()   //获得触摸点在整个屏幕的 Y 轴坐标。

一次完整的触控事件:手指落下(ACTION_DOWN) -> 多次移动(ACTION_MOVE) -> 离开(ACTION_UP),针对单点触控的事件处理一般是这样写的:

@Override
public boolean onTouchEvent(MotionEvent event) {
    // ▼ 注意这里使用的是 getAction(),先埋一个小尾巴。
    switch (event.getAction()){
        case MotionEvent.ACTION_DOWN:
            // 手指按下
            break;
        case MotionEvent.ACTION_MOVE:
            // 手指移动
            break;
        case MotionEvent.ACTION_UP:
            // 手指抬起
            break;
        case MotionEvent.ACTION_CANCEL:
            // 事件被拦截 
            break;
        case MotionEvent.ACTION_OUTSIDE:
            // 超出区域 
            break;
    }
    return super.onTouchEvent(event);
}

我们来看看4.1.40中示例View1在消费事件后的日志:

I/MainActivity: dispatchTouchEvent开始     MotionEvent.ACTION_DOWN
I/RootView: dispatchTouchEvent开始     MotionEvent.ACTION_DOWN
I/RootView: onInterceptTouchEvent开始  MotionEvent.ACTION_DOWN
I/ViewGroupA: dispatchTouchEvent开始     MotionEvent.ACTION_DOWN
I/ViewGroupA: onInterceptTouchEvent开始  MotionEvent.ACTION_DOWN
I/View1: dispatchTouchEvent开始     MotionEvent.ACTION_DOWN
I/View1: onTouchEvent开始           MotionEvent.ACTION_DOWN


I/MainActivity: dispatchTouchEvent开始     MotionEvent.ACTION_MOVE
I/RootView: dispatchTouchEvent开始     MotionEvent.ACTION_MOVE
I/RootView: onInterceptTouchEvent开始  MotionEvent.ACTION_MOVE
I/ViewGroupA: dispatchTouchEvent开始     MotionEvent.ACTION_MOVE
I/ViewGroupA: onInterceptTouchEvent开始  MotionEvent.ACTION_MOVE
I/View1: dispatchTouchEvent开始     MotionEvent.ACTION_MOVE
I/View1: onTouchEvent开始           MotionEvent.ACTION_MOVE

I/MainActivity: dispatchTouchEvent开始     MotionEvent.ACTION_UP
I/RootView: dispatchTouchEvent开始     MotionEvent.ACTION_UP
I/RootView: onInterceptTouchEvent开始  MotionEvent.ACTION_UP
I/ViewGroupA: dispatchTouchEvent开始     MotionEvent.ACTION_UP
I/ViewGroupA: onInterceptTouchEvent开始  MotionEvent.ACTION_UP
I/View1: dispatchTouchEvent开始     MotionEvent.ACTION_UP
I/View1: onTouchEvent开始           MotionEvent.ACTION_UP

1.1 ACTION_CANCEL

ACTION_CANCEL 的触发条件是事件被上层拦截,然而我们在 事件分发机制原理 一文中了解到当事件被上层 View 拦截的时候,ChildView 是收不到任何事件的,ChildView 收不到任何事件,自然也不会收到 ACTION_CANCEL 了,这不是相互违背么?

事实上这里表示的是,当上层 View 回收事件处理权的时候,ChildView 才会收到一个 ACTION_CANCEL 事件。 譬如最开始是ChildView消费了down事件,但是当move和up的过程中,父布局截获了事件并消费,此时chlid会收到 ACTION_CANCEL 事件

例如:

  • 上层 View 是一个 RecyclerView,它收到了一个 ACTION_DOWN 事件,由于这个可能是点击事件,所以它先传递给对应 ItemView,询问 ItemView 是否需要这个事件。
  • 接下来又传递过来了一个 ACTION_MOVE 事件,且移动的方向和 RecyclerView 的可滑动方向一致,所以 RecyclerView 判断这个事件是滚动事件,于是要收回事件处理权,这时候对应的 ItemView 会收到一个 ACTION_CANCEL ,并且不会再收到后续事件

1.2 ACTION_OUTSIDE

ACTION_OUTSIDE的触发条件更加奇葩,从字面上看,outside 意思不就是超出区域么?然而不论你如何滑动超出控件区域都不会触发 ACTION_OUTSIDE 这个事件

正常情况下,如果初始点击位置在该视图区域之外,该视图根本不可能会收到事件,然而,万事万物都不是绝对的,肯定还有一些特殊情况,你可曾还记得点击 Dialog 区域外关闭吗?Dialog 就是一个特殊的视图(没有占满屏幕大小的窗口),能够接收到视图区域外的事件(虽然在通常情况下你根本用不到这个事件),除了 Dialog 之外,你最可能看到这个事件的场景是悬浮窗,当然啦,想要接收到视图之外的事件需要一些特殊的设置

设置视图的 WindowManager 布局参数的 flags为FLAG_WATCH_OUTSIDE_TOUCH,这样点击事件发生在这个视图之外时,该视图就可以接收到一个 ACTION_OUTSIDE 事件。

参见StackOverflow:How to dismiss the dialog with click on outside of the dialog?

1.3 move历史数据(批处理)

由于我们的设备非常灵敏,手指稍微移动一下就会产生一个移动事件,所以移动事件会产生的特别频繁,为了提高效率,系统会将近期的多个移动事件(move)按照事件发生的顺序进行排序打包放在同一个 MotionEvent 中,与之对应的产生了以下方法:

事件简介
getHistorySize()获取历史事件集合大小
getHistoricalX(int pos)获取第pos个历史事件x坐标(pos < getHistorySize())
getHistoricalY(int pos)获取第pos个历史事件y坐标(pos < getHistorySize())
getHistoricalX (int pin, int pos)获取第pin个手指的第pos个历史事件x坐标(pin < getPointerCount(), pos < getHistorySize() )
getHistoricalY (int pin, int pos)获取第pin个手指的第pos个历史事件y坐标(pin < getPointerCount(), pos < getHistorySize() )
  1. pin 全称是 pointerIndex,表示第几个手指,此处为了节省空间使用了缩写。
  2. 历史数据只有 ACTION_MOVE 事件。
  3. 历史数据单点触控和多点触控均可以用

二、多点触控

首先要解决的问题就是 多个手指同时按在屏幕上,会产生很多的事件,这些事件该如何区分呢?为了区分这些事件,工程师们用了一个很简单的办法—编号: 当手指第一次按下时产生一个唯一的号码,手指抬起或者事件被拦截就回收编号,就这么简单。

第一次按下的手指特殊处理作为主指针,之后按下的手指作为辅助指针,然后随之衍生出来了以下事件(注意增加的事件和事件简介的变化):

事件简介
ACTION_DOWN第一个 手指 初次接触到屏幕 时触发。
ACTION_MOVE手指 在屏幕上滑动 时触发,会多次触发。
ACTION_UP最后一个 手指 离开屏幕 时触发。
ACTION_POINTER_DOWN有非主要的手指按下(即按下之前已经有手指在屏幕上)。
ACTION_POINTER_UP有非主要的手指抬起(即抬起之后仍然有手指在屏幕上)。
以下事件类型不推荐使用------------------
ACTION_POINTER_1_DOWN第 2 个手指按下,已废弃,不推荐使用。
ACTION_POINTER_2_DOWN第 3 个手指按下,已废弃,不推荐使用。
ACTION_POINTER_3_DOWN第 4 个手指按下,已废弃,不推荐使用。
ACTION_POINTER_1_UP第 2 个手指抬起,已废弃,不推荐使用。
ACTION_POINTER_2_UP第 3 个手指抬起,已废弃,不推荐使用。
ACTION_POINTER_3_UP第 4 个手指抬起,已废弃,不推荐使用。
getActionMasked()//与 getAction() 类似,多点触控必须使用这个方法获取事件类型。
getActionIndex()//获取该事件是哪个指针(手指)产生的。
getPointerCount()//获取在屏幕上手指的个数。
getPointerId(int pointerIndex)//获取一个指针(手指)的唯一标识符ID,在手指按下和抬起之间ID始终不变。
findPointerIndex(int pointerId)//通过PointerId获取到当前状态下PointIndex,之后通过PointIndex获取其他内容。
getX(int pointerIndex)//获取某一个指针(手指)的X坐标
getY(int pointerIndex)//获取某一个指针(手指)的Y坐标
//int action = event.getAction() & MotionEvent.ACTION_MASK;
//int index = (event.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK)
        >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
int index = event.getActionIndex();//2.2以上版本支持

switch (event.getActionMasked()) {
    case MotionEvent.ACTION_DOWN:
        Log.e(TAG,"第1个手指按下");
        break;
    case MotionEvent.ACTION_UP:
        Log.e(TAG,"最后1个手指抬起");
        break;
    case MotionEvent.ACTION_POINTER_DOWN:
        Log.e(TAG,"第"+(index+1)+"个手指按下");
        break;
    case MotionEvent.ACTION_POINTER_UP:
        Log.e(TAG,"第"+(index+1)+"个手指抬起");
        break;
}

2.1 getAction() 与 getActionMasked()

  • getActionMasked() 方法,这个方法可以清除index数值,让其变成一个标准的事件类型
    • 多点触控时必须使用 getActionMasked() 来获取事件类型
    • 单点触控时由于事件数值不变,使用 getAction() 和 getActionMasked() 两个方法都可以
  • getAction() 获取事件类型:类型 + 编号
  • getActionIndex() 获取事件编号
    • getActionIndex() 只在 down 和 up 时有效,move 时是无效的

当多个手指在屏幕上按下的时候,会产生大量的事件,如何在获取事件类型的同时区分这些事件就是一个大问题了

int类型共32位(0x00000000),他们用最低8位(0x000000ff)表示事件类型,再往前的8位(0x0000ff00)表示事件编号,以手指按下为例讲解数值是如何合成的:

手指按下触发事件(数值)
第1个手指按下ACTION_DOWN (0x00000000)
第2个手指按下ACTION_POINTER_DOWN (0x00000105)
第3个手指按下ACTION_POINTER_DOWN (0x00000205)
第4个手指按下ACTION_POINTER_DOWN (0x00000305)

2.2 index 和 pointId 的变化规则

在 2.2 版本以上,我们可以通过 getActionIndex() 轻松获取到事件的索引(Index),但是这个事件索引的变化还是有点意思的,Index 变化有以下几个特点:

  1. 从 0 开始,自动增长。
  2. 如果之前落下的手指抬起,后面手指的 Index 会随之减小。
  3. Index 变化趋向于第一次落下的数值(落下手指时,前面有空缺会优先填补空缺)。
  4. 对 move 事件无效。

参考文献

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值