自定义 View:多点触控 (一)-- 手指接力

本篇为系列文章
一个跟随手指滑动的图片 – 单点触摸
自定义 View:多点触控 (一)-- 手指接力
自定义View:多点触控(二)-- 多指协作
自定义 View:多点触控 (三)-- 各自为战

多点触控大致可分为三种类型:

  1. 接力型
  2. 协作型
  3. 各自为战

那么,默认的多指操作是上面的哪一种呢,很遗憾,都不是,我们可以运行我们上一篇文章讲的,一个可拖动的图片,这里面没有处理多指操作,当我们两个手指同时进行操作的时候,会发现效果是这样:第一个手指正常操作,第二个手指放上去开始滑动但是没有反应,只有当第一个手指移除,才会响应第二个手指的操作,当第一个手指放回时,第二个手指立即失效,直接响应第一个手指的操作

为什么会这样呢,在讲这个之前,先要说一个概念,单指操作的事件序列

一个最简单的事件序列通常是这样的

ACTION_DOWN
ACTION_MOVE
ACTION_MOVE
ACTION_MOVE
ACTION_MOVE
ACTION_MOVE
ACTION_MOVE
ACTION_MOVE
ACTION_MOVE
ACTION_UP

从按下到抬起,算作一次事件,而ACTION_MOVE是会多次触发的,因为哪怕是你以为你没动,但其实在屏幕上哪怕是微微的晃动都会触发

还有一种是ACTION_CANNEL不在讨论当中,因为他不是由用户触发的,是由父View触发的,当事件被父View抢夺就会触发被抢夺View的ACTION_CANNEL,是让子View处理后事,也就是擦屁股用的

那么多指操作的事件序列是怎么样的呢?

ACTION_DOWN
ACTION_MOVE
ACTION_MOVE
ACTION_POINTER_DOWN
ACTION_MOVE
ACTION_MOVE
ACTION_POINTER_DOWN
ACTION_MOVE
ACTION_POINTER_UP
ACTION_MOVE
ACTION_POINTER_UP
ACTION_MOVE
ACTION_MOVE
ACTION_UP

每多一个手指会一对ACTION_POINTER_DOWNACTION_POINTER_UP

注意,以上每一个序列都是站在一个View的角度来说的,而不是站在不同的手指的角度

是同一个View的多个手指,会在同一个事件序列里面,就像以上所描述的那样

每一个事件都会对应一个坐标,而事件是由手指触发的,也就是对应一个point, 也是站在View的角度来说,是针对View的

那么不同的手指如何区分呢

ACTION_DOWN		p(x,y,index,id)
ACTION_MOVE		p(x,y,index,id)
ACTION_MOVE		p(x,y,index,id)
ACTION_POINTER_DOWN		p(x,y,index,id)		p(x,y,index,id)
ACTION_MOVE		p(x,y,index,id)		p(x,y,index,id)
ACTION_MOVE		p(x,y,index,id)		p(x,y,index,id)
ACTION_POINTER_DOWN
ACTION_MOVE
ACTION_POINTER_UP
ACTION_MOVE
ACTION_POINTER_UP
ACTION_MOVE
ACTION_MOVE
ACTION_UP

看以上序列集,当有一个**p()的时候,代表这个View有一个手指在操作,当有两个p()**时,代表有两个手指在操作

上代码,基于上一篇的单指操作进行改造

import android.content.Context
import android.graphics.BitmapFactory
import android.graphics.Canvas
import android.graphics.Paint
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.View
import com.example.viewtest.R

class MultiTouchView(context: Context, attrs: AttributeSet?) : View(context, attrs) {
    
    

    private val bitmap = BitmapFactory.decodeResource(resources, R.drawable.ic_choice_select)
    private val paint = Paint(Paint.ANTI_ALIAS_FLAG)
    private var offsetX = 0f
    private var offsetY = 0f
    private var downX = 0f
    private var downY = 0f
    private var originOffsetX = 0f
    private var originOffsetY = 0f

    override fun onDraw(canvas: Canvas?) {
    
    
        super.onDraw(canvas)
        // 绘制时候使用更新后的坐标
        canvas?.drawBitmap(bitmap, offsetX, offsetY, paint)
    }

    override fun onTouchEvent(event: MotionEvent?): Boolean {
    
    
        when (event?.actionMasked) {
    
    
            MotionEvent.ACTION_DOWN -> {
    
    
                // 记录手指按下的坐标
                downX = event.x
                downY = event.y
            }
            MotionEvent.ACTION_MOVE -> {
    
    
                // 记录最新的手指位置
                offsetX = event.x - downX + originOffsetX
                offsetY = event.y - downY + originOffsetY
                // 触发重绘
                invalidate()
            }
            MotionEvent.ACTION_UP -> {
    
    
                originOffsetX = offsetX
                originOffsetY = offsetY
            }
        }
        return true
    }
}

这里需要注意的是:

	override fun onTouchEvent(event: MotionEvent?): Boolean {
    
    
        when (event?.actionMasked) {
    
    
        }
    }

使用 event?.actionMasked 来处理多指操作事件

    override fun onTouchEvent(event: MotionEvent?): Boolean {
    
    
        when (event?.actionMasked) {
    
    
            MotionEvent.ACTION_DOWN -> {
    
    
                trackingPointerId = event.getPointerId(0)
                // 记录手指按下的坐标
                downX = event.x
                downY = event.y
            }
            // 新增的手指按下会触发
            MotionEvent.ACTION_POINTER_DOWN -> {
    
    
                val actionIndex = event.actionIndex
                trackingPointerId = event.getPointerId(actionIndex)
                // 记录新手指按下的坐标
                downX = event.getX(actionIndex)
                downY = event.getY(actionIndex)

                originOffsetX = offsetX
                originOffsetY = offsetY
            }
            MotionEvent.ACTION_MOVE -> {
    
    
                val index = event.findPointerIndex(trackingPointerId)
                // 记录最新的手指位置
                offsetX = event.getX(index) - downX + originOffsetX
                offsetY = event.getY(index) - downY + originOffsetY
                // 触发重绘
                invalidate()
            }
            MotionEvent.ACTION_UP -> {
    
    
                originOffsetX = offsetX
                originOffsetY = offsetY
            }
        }
        return true
    }

这里只看onTouchEvent的相关代码,这里新增了ACTION_POINTER_UP类型,然后所有的坐标获取都改成了使用getX或者getY了,之前写的其实默认的index是0而已,而现在的index是通过id获取的,这也就解释了事件序列集中的所有参数的作用

到此为止,我们运行,结果第二根手指的事件接管是好使的

然后我们需要处理非最后一根手指抬起的操作,其实我们处理非第一根手指的按下或者抬起,都是在处理接棒的问题,最后一根手指没人接了,就不需要考虑了

	override fun onTouchEvent(event: MotionEvent?): Boolean {
    
    
        when (event?.actionMasked) {
    
    
            ......
            // 当前 view 上所剩手指大于 1 时,有手指抬起会触发
            MotionEvent.ACTION_POINTER_UP -> {
    
    
                val actionIndex = event.actionIndex
                val pointerId = event.getPointerId(actionIndex)
                // 判断抬起手指的 ID 是否是正在操作的手指 ID,如果是,则需要选一根手指来接管操作
                if (pointerId == trackingPointerId) {
    
    
                    // 需要选一根手指来接管操作,具体选哪个手指,需要我们自己写算法,这里选最后一根手指
                    // 注意,这个时候去获取当前View的所有手指的时候,是包括当前正在抬起的手指的
                    // 如果抬起的手指就是最后一根手指,那么我自己是不可能接棒我自己的
                    val newIndex = if (actionIndex == event.pointerCount - 1) {
    
    
                        event.pointerCount - 2
                    } else {
    
    
                        event.pointerCount - 1
                    }

                    trackingPointerId = event.getPointerId(newIndex)

                    downX = event.getX(newIndex)
                    downY = event.getY(newIndex)

                    originOffsetX = offsetX
                    originOffsetY = offsetY
                }

            }
            .....
        }
        return true
    }

还是看onTouchEvent方法,这里只看ACTION_POINTER_UP事件,看注释描述

运行看结果:

  1. 第一根手指按下移动、第二根手指按下移动,第二根手指顺利接管,第一根手指失效,第一根手指先抬起,剩下的手指仍然正常操纵,没有问题
  2. 第一根手指按下移动、第二根手指按下移动,第二根手指顺利接管,第一根手指失效,第二根手指先抬起,剩下的手指仍然正常操纵,没有问题

完美实现多指操作类型中的第一种类型,接力类型

猜你喜欢

转载自blog.csdn.net/qq_35178391/article/details/132700645