自定义View:多点触控(二)-- 多指协作

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

本文是系列文章,建议先去看之前的文章,否则可能会无法理解接下来的内容

协作型的本质是,找到所有手指的中心坐标,也就是焦点,围绕焦点坐标搞事情

我们先贴一下上一篇文章的最重代码,我们需要在上一篇的基础上改造

package com.example.viewtest.view

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

    private var trackingPointerId = 0

    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 -> {
    
    
                trackingPointerId = event.getPointerId(0)
                // 记录手指按下的坐标
                downX = event.x
                downY = event.y

                originOffsetX = offsetX
                originOffsetY = offsetY
            }
            // 新增的手指按下会触发
            MotionEvent.ACTION_POINTER_DOWN -> {
    
    
                val actionIndex = event.actionIndex
                trackingPointerId = event.getPointerId(actionIndex)
                // 记录新手指按下的坐标
                downX = event.getX(actionIndex)
                downY = event.getY(actionIndex)

                originOffsetX = offsetX
                originOffsetY = offsetY
            }
            // 当前 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
                }

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

多指的中心点,也就是焦点怎么算呢?

举例,两个手指的中心点就是两个两个点的横坐标相加/2,两个点的纵坐标相加/2

三个点呢?也是一样的

n个点也一样

那我们就可以写代码了

    override fun onTouchEvent(event: MotionEvent): Boolean {
    
    
        val focusX: Float
        val focusY: Float
        val pointerCount = event.pointerCount
        var sumX = 0f
        var sumY = 0f

        for (i in 0 until pointerCount) {
    
    
            sumX += event.getX(i)
            sumY += event.getY(i)
        }

        focusX = sumX / pointerCount
        focusY = sumY / pointerCount
        
        ......
        
    }

以上就是计算一个焦点的过程,可以发现,焦点计算出来之后,本质是View跟随这个焦点移动,具体有多少个手指都没有关系了,所以说又成了最初的单指操作

那么我们把剩下的代码的单指逻辑使用的手指换成这个焦点就好了

扫描二维码关注公众号,回复: 16497085 查看本文章
    override fun onTouchEvent(event: MotionEvent): Boolean {
    
    
        val focusX: Float
        val focusY: Float
        val pointerCount = event.pointerCount
        var sumX = 0f
        var sumY = 0f

        for (i in 0 until pointerCount) {
    
    
            sumX += event.getX(i)
            sumY += event.getY(i)
        }

        focusX = sumX / pointerCount
        focusY = sumY / pointerCount

        when (event.actionMasked) {
    
    
            MotionEvent.ACTION_DOWN -> {
    
    
                // 记录焦点的坐标
                downX = focusX
                downY = focusY

                originOffsetX = offsetX
                originOffsetY = offsetY
            }
            .......
            MotionEvent.ACTION_MOVE -> {
    
    
                // 记录最新的焦点位置
                offsetX = focusX - downX + originOffsetX
                offsetY = focusY - downY + originOffsetY
                // 触发重绘
                invalidate()
            }
        }
    }

以上主要体现的是获取焦点替换成虚拟焦点

那么还用处理多指吗?如果不处理的话,在有新的手指按下的时候,会瞬间改变焦点的位置,会导致图片瞬间移动,所以说我们还是需要处理多指的

但是这里的处理就不需要那么复杂了,不管是第几根手指按下还是抬起,都只需要做相同的事情,也是之前单点触摸的逻辑:重置焦点坐标

    override fun onTouchEvent(event: MotionEvent): Boolean {
    
    
        val focusX: Float
        val focusY: Float
        val pointerCount = event.pointerCount
        var sumX = 0f
        var sumY = 0f

        for (i in 0 until pointerCount) {
    
    
            sumX += event.getX(i)
            sumY += event.getY(i)
        }

        focusX = sumX / pointerCount
        focusY = sumY / pointerCount

        when (event.actionMasked) {
    
    
            MotionEvent.ACTION_DOWN, MotionEvent.ACTION_POINTER_DOWN, MotionEvent.ACTION_POINTER_UP -> {
    
    
                trackingPointerId = event.getPointerId(0)
                // 记录焦点的坐标
                downX = focusX
                downY = focusY

                originOffsetX = offsetX
                originOffsetY = offsetY
            }

            MotionEvent.ACTION_MOVE -> {
    
    
                // 记录最新的焦点位置
                offsetX = focusX - downX + originOffsetX
                offsetY = focusY - downY + originOffsetY
                // 触发重绘
                invalidate()
            }
        }
        return true
    }

我们运行以上代码测试:

  1. 一个手指滑动,没有问题
  2. 增加一个手指,一个动一个不动(同向),图片半速移动,没有问题
  3. 两个手指一起向相同方向移动,全速,没有问题
  4. 抬起一个手指,坏了,图像出现了跳跃

我明明处理了多指的抬起啊,为什么还会出现问题呢,其实问题出现在event.pointerCount,之前我们讲过,这个获取的数量是包括正在抬起的手指的,所以说我们手指抬起的时候,返回的数量和实际的数量是对应不上的

所以说我们需要处理两个地方

一个是ACTION_POINTER_UP时,我们需要将返回的手指数量 -1,另一个是在循环计算偏移的时候,要减去这个偏移

    override fun onTouchEvent(event: MotionEvent): Boolean {
    
    
        val focusX: Float
        val focusY: Float
        var pointerCount = event.pointerCount
        var sumX = 0f
        var sumY = 0f

        // 标记,是否是多指的手指抬起
        val isPointerUp = event.actionMasked == MotionEvent.ACTION_POINTER_UP

        for (i in 0 until pointerCount) {
    
    
            // 判断是否是抬起,并且是当前手指,当前手指的抬起不应该参与偏移计算
            val b = (isPointerUp && i == event.actionIndex)
            if (!b) {
    
    
                sumX += event.getX(i)
                sumY += event.getY(i)
            }
        }

        // 如果是多指抬起,则应该少一个数量参与计算
        if (isPointerUp) {
    
    
            pointerCount--
        }
        focusX = sumX / pointerCount
        focusY = sumY / pointerCount
    }

测试

  1. 一个手指滑动,没有问题
  2. 增加一个手指,一个动一个不动(同向),图片半速移动,没有问题
  3. 两个手指一起向相同方向移动,全速,没有问题
  4. 抬起一个手指,没有问题

好了,我们协作类型的到此讲解完毕,我们贴一下全部代码

package com.example.viewtest.view

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 MultiTouchView2 (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 {
    
    
        val focusX: Float
        val focusY: Float
        var pointerCount = event.pointerCount
        var sumX = 0f
        var sumY = 0f

        // 标记,是否是多指的手指抬起
        val isPointerUp = event.actionMasked == MotionEvent.ACTION_POINTER_UP

        for (i in 0 until pointerCount) {
    
    
            // 判断是否是抬起,并且是当前手指,当前手指的抬起不应该参与偏移计算
            val b = (isPointerUp && i == event.actionIndex)
            if (!b) {
    
    
                sumX += event.getX(i)
                sumY += event.getY(i)
            }
        }

        // 如果是多指抬起,则应该少一个数量参与计算
        if (isPointerUp) {
    
    
            pointerCount--
        }
        focusX = sumX / pointerCount
        focusY = sumY / pointerCount

        when (event.actionMasked) {
    
    
            MotionEvent.ACTION_DOWN, MotionEvent.ACTION_POINTER_DOWN, MotionEvent.ACTION_POINTER_UP -> {
    
    
                // 记录焦点的坐标
                downX = focusX
                downY = focusY

                originOffsetX = offsetX
                originOffsetY = offsetY
            }

            MotionEvent.ACTION_MOVE -> {
    
    
                // 记录最新的焦点位置
                offsetX = focusX - downX + originOffsetX
                offsetY = focusY - downY + originOffsetY
                // 触发重绘
                invalidate()
            }
        }
        return true
    }
}

猜你喜欢

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