多指触摸处理

一.前言

在开发中,我们经常会使用自定义View进行拖动和缩放,拖动一般是单指触摸,但是实际情况我们也可以使用多指进行触摸移动,如果这是我们进行抬起

二.了解触摸事件

2.1 了解MotionEvent中手指的index和id

在MotionEvent中,存储了每个手指的index和id:

  • Index: 每个MotionEvent在数组中存储了手指的信息,index表示手指在数组中的位置,开发中我们也是通过index作为参数来获取相关信息,比如event.getX(int pointerIndex),event.getY(int pointerIndex)来获取MotionEvent的坐标信息,但是当有多个手指放在屏幕上,同时又抬起相关的手指,那么这个pointerIndex会发生改变。
  • ID:每个手指都有一个ID映射,它在整个触摸中保持持久性。

2.2 了解MotionEvent中Event

当多个手指同时触摸屏幕的时候,系统会生成以下事件:

  • ACTION_DOWN:当第一个手指触摸到屏幕的时候,生成事件。在该事件中pointerIndex始终为0.
  • ACTION_POINTER_DOWN:出了第一个触摸屏幕外的手指触摸屏幕的时候会受到该事件,该事件的pointerIndex可通过event.getActionIndex()获取。
  • ACTION_MOVE:当手指按下的过程中发生移动
  • ACTION_POINTER_UP:不是主的手指抬起
  • ACTION_UP:当最后一个手指离开屏幕

2.3 了解MotionEvent的事件流程

每个手指在MotionEvent中的顺序是为定义的。因此,手指保持活动的状态下,index是可以从一个事件更改到下一个事件的。我们需要使用event.getPointerId()来来获取手指的Id,以便在后续的MotionEvent中追踪对应的手指,再通过findPointerIdnex()来获取对应手指ID的在MotionEvent中的index。

针对上述这个描述我们举个实例来说明一下,定义一个View,进行拖动处理,代码如下:

class MoveView : View {
    constructor(context: Context?) : super(context)
    constructor(context: Context?, attributeSet: AttributeSet) : super(context, attributeSet)
    constructor(context: Context?, attributeSet: AttributeSet, defStyleAttr: Int) : super(context, attributeSet, defStyleAttr)
    private var mLastTouchX = 0f
    private var mLastTouchY = 0f

    override fun onTouchEvent(event: MotionEvent): Boolean {
        when (event.action) {
            MotionEvent.ACTION_DOWN -> {
                mLastTouchX = event.x
                mLastTouchY = event.y
            }
            MotionEvent.ACTION_MOVE -> {
                translationX = translationX + event.x - mLastTouchX
                translationY = translationY + event.y - mLastTouchY
            }
        }

        return true
    }
}

单个手指触摸在在MoveView上的时候正常,但当有个两个手指在屏幕上,手指不停的切换抬起和按下的时候会出现如下效果:
image

这种效果对应一个有追求的程序员肯定是不能满足的。操作上面的这个View,会感觉抖动和不平滑。那么出现这个问题的原生是什么呢?修改一下代码,将事件的日志输出:

    override fun onTouchEvent(event: MotionEvent): Boolean {
        when (event.actionMasked) {
            MotionEvent.ACTION_DOWN -> {
                val actionIndex = event.actionIndex
                val mActivePointerId = event.getPointerId(actionIndex)
                mLastTouchX = event.x
                mLastTouchY = event.y
                log("MotionEvent.ACTION_DOWN:-- actionIndex:$actionIndex---activePointerId:$mActivePointerId")

            }
            MotionEvent.ACTION_MOVE -> {
                translationX = translationX + event.x - mLastTouchX
                translationY = translationY + event.y - mLastTouchY
            }
            MotionEvent.ACTION_UP -> {
                log("MotionEvent.ACTION_UP ")
            }
            MotionEvent.ACTION_POINTER_DOWN -> {
                val actionIndex = event.actionIndex
                val mActivePointerId = event.getPointerId(actionIndex)
                log("MotionEvent.ACTION_POINTER_DOWN:-- actionIndex:$actionIndex---activePointerId:$mActivePointerId")
            }
            MotionEvent.ACTION_POINTER_UP -> {
                val actionIndex = event.actionIndex
                val mActivePointerId = event.getPointerId(actionIndex)
                log("MotionEvent.ACTION_POINTER_UP:-- actionIndex:$actionIndex---activePointerId:$mActivePointerId")
            }

        }
        return true
    }

得到如下日志:

D/MoveView: MotionEvent.ACTION_DOWN:-- actionIndex:0---activePointerId:0
D/MoveView: MotionEvent.ACTION_POINTER_DOWN:-- actionIndex:1---activePointerId:1
D/MoveView: MotionEvent.ACTION_POINTER_UP:-- actionIndex:1---activePointerId:1
D/MoveView: MotionEvent.ACTION_POINTER_DOWN:-- actionIndex:1---activePointerId:1
D/MoveView: MotionEvent.ACTION_POINTER_UP:-- actionIndex:0---activePointerId:0
D/MoveView: MotionEvent.ACTION_DOWN:-- actionIndex:0---activePointerId:0
D/MoveView: MotionEvent.ACTION_POINTER_DOWN:-- actionIndex:1---activePointerId:1
D/MoveView: MotionEvent.ACTION_POINTER_UP:-- actionIndex:0---activePointerId:0
D/MoveView: MotionEvent.ACTION_POINTER_DOWN:-- actionIndex:0---activePointerId:0
D/MoveView: MotionEvent.ACTION_POINTER_UP:-- actionIndex:0---activePointerId:0
D/MoveView: MotionEvent.ACTION_DOWN:-- actionIndex:0---activePointerId:0
D/MoveView: MotionEvent.ACTION_POINTER_DOWN:-- actionIndex:1---activePointerId:1
D/MoveView: MotionEvent.ACTION_POINTER_UP:-- actionIndex:0---activePointerId:0
D/MoveView: MotionEvent.ACTION_DOWN:-- actionIndex:0---activePointerId:0
D/MoveView: MotionEvent.ACTION_POINTER_DOWN:-- actionIndex:1---activePointerId:1
D/MoveView: MotionEvent.ACTION_POINTER_UP:-- actionIndex:0---activePointerId:0
D/MoveView: MotionEvent.ACTION_DOWN:-- actionIndex:0---activePointerId:0
D/MoveView: MotionEvent.ACTION_POINTER_DOWN:-- actionIndex:1---activePointerId:1
D/MoveView: MotionEvent.ACTION_POINTER_UP:-- actionIndex:0---activePointerId:0
D/MoveView: MotionEvent.ACTION_DOWN:-- actionIndex:0---activePointerId:0
D/MoveView: MotionEvent.ACTION_POINTER_DOWN:-- actionIndex:1---activePointerId:1
D/MoveView: MotionEvent.ACTION_POINTER_UP:-- actionIndex:0---activePointerId:0
D/MoveView: MotionEvent.ACTION_DOWN:-- actionIndex:0---activePointerId:0
D/MoveView: MotionEvent.ACTION_POINTER_DOWN:-- actionIndex:1---activePointerId:1
D/MoveView: MotionEvent.ACTION_POINTER_UP:-- actionIndex:0---activePointerId:0
D/MoveView: MotionEvent.ACTION_POINTER_DOWN:-- actionIndex:0---activePointerId:0
D/MoveView: MotionEvent.ACTION_POINTER_UP:-- actionIndex:0---activePointerId:0
D/MoveView: MotionEvent.ACTION_POINTER_DOWN:-- actionIndex:0---activePointerId:0
D/MoveView: MotionEvent.ACTION_POINTER_UP:-- actionIndex:0---activePointerId:0
D/MoveView: MotionEvent.ACTION_POINTER_DOWN:-- actionIndex:0---activePointerId:0
D/MoveView: MotionEvent.ACTION_POINTER_UP:-- actionIndex:0---activePointerId:0
D/MoveView: MotionEvent.ACTION_POINTER_DOWN:-- actionIndex:0---activePointerId:0
D/MoveView: MotionEvent.ACTION_POINTER_UP:-- actionIndex:0---activePointerId:0

从上面的日志可以看出,最后几行的日志输出,屏幕上已经有了一个手指,但是再按下一个手指的时候,分配的actionIndex为0,理想情况应该是1,这里就导致了我们在ACTION_MOVE中使用event.getX()和event.getY()取出的坐标是当前手指按下的坐标以及mLastTouchX和mLastTouchY是上一个手指ACTION_DOWN时的值,因此计算的结果就不是我们期望的值,直接的效果就是上图中的抖动效果。因此这里这里我们需要处理MotionEvent.ACTION_POINTER_UP的手指ID,在evnet.getX()和event.getY()中传入id对应的手指index。代码如下:

//取出当前事件分配的手指Index
val pointerIndex = event.actionIndex
//根据索引得到手指ID
val pointerId = event.getPointerId(pointerIndex)
//
if (pointerId == mActivePointerId) {
    //从上图的日志的后几行中,我可以看出,当屏幕上有一个手指,再次按下和抬起的时候,分配的手指index为0,这里我们就需要或index为1的手指索引
    val newPointerIndex = if (pointerIndex == 0) 1 else 0
    //重新改变按下触摸点的坐标
    mLastTouchX = event.getX(newPointerIndex)
    mLastTouchY = event.getY(newPointerIndex)
    //获取新activiePointerId
    mActivePointerId = event.getPointerId(newPointerIndex)
}

完整代码:

    private val INVALID_POINTER_ID = -1
    private var mActivePointerId = INVALID_POINTER_ID

    private var mLastTouchX = 0f
    private var mLastTouchY = 0f
    
    override fun onTouchEvent(event: MotionEvent): Boolean {
        when (event.actionMasked) {
            MotionEvent.ACTION_DOWN -> {
                val pointerIndex = event.actionIndex
                mActivePointerId = event.getPointerId(0)
                mLastTouchX = event.getX(pointerIndex)
                mLastTouchY = event.getY(pointerIndex)
            }
            MotionEvent.ACTION_MOVE -> {
                val pointerIndex = event.findPointerIndex(mActivePointerId)
                translationX = translationX + event.getX(pointerIndex) - mLastTouchX
                translationY = translationY + event.getY(pointerIndex) - mLastTouchY
            }
            MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
                mActivePointerId = INVALID_POINTER_ID
            }
            MotionEvent.ACTION_POINTER_UP -> {
                val pointerIndex = event.actionIndex
                val pointerId = event.getPointerId(pointerIndex)
                if (pointerId == mActivePointerId) {
                    val newPointerIndex = if (pointerIndex == 0) 1 else 0
                    mLastTouchX = event.getX(newPointerIndex)
                    mLastTouchY = event.getY(newPointerIndex)
                    mActivePointerId = event.getPointerId(newPointerIndex)
                }
            }

        }
        return true
    }
}

三.总结

  1. 多指触摸事件需要使用actionMasked来进行判断处理
  2. 如果开发中遇到类似的滑动问题,可以参考ScollView,RecycleView等的处理方式
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值