Android multi-touch

Three types: relay type/matching type/individual type

Single touch

package com.example.myapplication.view

import android.content.Context
import android.graphics.Canvas
import android.graphics.Paint
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.View
import com.example.myapplication.dp

class MultiTouchView1(context: Context, attrs: AttributeSet) : View(context, attrs) {

    private val bitmap = getBitmap()
    private val paint = Paint(Paint.ANTI_ALIAS_FLAG)

    private var offsetX = 0f //Initial position
    private var offsetY = 0f

    private var downX = 0f //Pressed position
    private var downY = 0f

    private var originalOffsetX = 0f //Offset position
    private var originalOffsetY = 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
                originalOffsetX = offsetX
                originalOffsetY = offsetY

            }
            MotionEvent.ACTION_MOVE -> {
                offsetX = event.x - downX + originalOffsetX
                offsetY = event.y - downY + originalOffsetY
                invalidate()
            }
        }
        return true
    }
}

The touch event sequence is for the View rather than the finger

x, y, index, id belong to a point, a sequence, getX gets the finger position with index 0

 public final float getX() {
        return nativeGetAxisValue(mNativePtr, AXIS_X, 0, HISTORY_CURRENT);
    }

point_move two points, when the second finger is up, the index will be set to 0 instead of 1. Sometimes the 0th finger down with an index of 0 will be pressed, and then 0 will become 1

The function of index is to traverse each Point through index when a MotionEvent occurs.

getX(),getX(index),index is obtained through event.pointCount

for traverses event.point through getX(index). If a certain point is raised, an error will be reported and the Index cannot be found. In this case, you can search by id

 MotionEvent.ACTION_MOVE -> {

                event.getX(event.actionIndex) //The ID of the finger being pressed
               event.getX(event.findPointerIndex(downId)) //Get by ID
}

action_move is not suitable for this method, because when moving and updating in real time, there is no so-called Point, that is, index and id, which only make sense in down/up

Relay type

package com.example.myapplication.view

import android.content.Context
import android.graphics.Canvas
import android.graphics.Paint
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.View
import com.example.myapplication.dp


class MultiTouchView1(context: Context, attrs: AttributeSet) : View(context, attrs) {

    private val bitmap =getBitmap()
    private val paint = Paint(Paint.ANTI_ALIAS_FLAG)

    private var offsetX = 0f //Initial position
    private var offsetY = 0f

    private var downX = 0f //Pressed position
    private var downY = 0f

    private var originalOffsetX = 0f //Offset position
    private var originalOffsetY = 0f

    private var trackingPointerId = 0 //Currently pressed finger ID

    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
                originalOffsetX = offsetX
                originalOffsetY = offsetY

            }

            //Multiple
            MotionEvent.ACTION_POINTER_DOWN -> {
                val actionIndex = event.actionIndex
                trackingPointerId = event.getPointerId(actionIndex) //Get the ID of the pressed finger number
                //update takeover
                downX = event.getX(actionIndex)
                downY = event.getY(actionIndex)
                originalOffsetX = offsetX
                originalOffsetY = offsetY
            }

            MotionEvent.ACTION_POINTER_UP -> {
                val actionIndex = event.actionIndex
                val pointerId = event.getPointerId(actionIndex)
                if (pointerId == trackingPointerId) {
                    //If it is the finger being tracked, replace it
                    val newIndex = if (actionIndex == event.pointerCount - 1) {
                            event.pointerCount - 2
                        } else {
                            event.pointerCount - 1
                        }

                    trackingPointerId = event.getPointerId(newIndex) //Get the ID of the pressed finger number
                    //update takeover
                    downX = event.getX(newIndex)
                    downY = event.getY(newIndex)
                    originalOffsetX = offsetX
                    originalOffsetY = offsetY
                }
            }
            MotionEvent.ACTION_MOVE -> {

                val index = event.findPointerIndex(trackingPointerId)
                offsetX = event.getX(index) - downX + originalOffsetX
                offsetY = event.getY(index) - downY + originalOffsetY
                invalidate()
            }
        }
        return true
    }
}

Matching type:

package com.example.myapplication.view

import android.content.Context
import android.graphics.Canvas
import android.graphics.Paint
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.View
import com.example.myapplication.dp
import com.example.myapplication.getAvatar


//Swipe with two fingers
class MultiTouchView2(context: Context, attrs: AttributeSet) : View(context, attrs) {

    private val bitmap = getAvatar(resources, 200.dp.toInt())
    private val paint = Paint(Paint.ANTI_ALIAS_FLAG)

    private var offsetX = 0f //Initial position
    private var offsetY = 0f

    private var downX = 0f //Pressed position
    private var downY = 0f

    private var originalOffsetX = 0f //Offset position
    private var originalOffsetY = 0f


    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        canvas.drawBitmap(bitmap, offsetX, offsetY, paint)
    }

    override fun onTouchEvent(event: MotionEvent): Boolean {
        val focusX: Float//Add the two focus values /2
        val focusY : Float
        var pointerCount = event.pointerCount
        var sumX = 0f
        var sumY = 0f
        val inPointerUp = event.actionMasked == MotionEvent.ACTION_POINTER_UP //If it is lifted
        for (i in 0 until pointerCount){
            if (!(inPointerUp & amp; & amp; i == event.actionIndex)){ //The current position is not lifted, then calculate
                sumX + = event.getX(i) //Get the coordinates of each point
                sumY + = event.getY(i)
            }
        }

        if (inPointerUp){
            pointerCount -- //Handle additional offsets
        }
        focusX = sumX / pointerCount //Get the focus value. When lifted, the count will change
        focusY = sumY /pointerCount

        when (event.actionMasked) {
            MotionEvent.ACTION_DOWN,MotionEvent.ACTION_POINTER_DOWN, MotionEvent.ACTION_POINTER_UP -> {
                downX = focusX
                downY = focusY
                originalOffsetX = offsetX
                originalOffsetY = offsetY
            }

            MotionEvent.ACTION_MOVE -> {
                offsetX = focusX - downX + originalOffsetX
                offsetY = focusY - downY + originalOffsetY
                invalidate()
            }
        }
        return true
    }
}

Polydactyly

package com.example.myapplication.view

import android.content.Context
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.Path
import android.util.AttributeSet
import android.util.SparseArray
import android.view.MotionEvent
import android.view.View
import androidx.core.util.isEmpty
import com.example.myapplication.dp

class MultiTouchView3(context: Context, attrs: AttributeSet) : View(context, attrs) {
    private val paint = Paint(Paint.ANTI_ALIAS_FLAG)
    private var paths = SparseArray<Path>()

    init {
        paint.style = Paint.Style.STROKE
        paint.strokeWidth = 4.dp
        paint.strokeCap = Paint.Cap.ROUND
        paint.strokeJoin = Paint.Join.ROUND

    }
    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        for (i in 0 until paths.size()){
            val path = paths.valueAt(i)
            canvas.drawPath(path,paint)
        }

    }

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

            MotionEvent.ACTION_DOWN,MotionEvent.ACTION_POINTER_DOWN ->{
                val actionIndex = event.actionIndex
                val path = Path()
                path.moveTo(event.getX(actionIndex),event.getY(actionIndex))
                paths.append(event.getPointerId(actionIndex),path)
                invalidate()
            }

            MotionEvent.ACTION_MOVE ->{
                if (!paths.isEmpty()){
                    for (i in 0 until paths.size()){
                        val pointerId = event.getPointerId(i)
                        val path = paths.get(pointerId)
                        path.lineTo(event.getX(i),event.getY(i))
                    }
                    invalidate()
                }

            }
            MotionEvent.ACTION_UP,MotionEvent.ACTION_POINTER_UP -> {
                val actionIndex = event.actionIndex
                val pointerId = event.getPointerId(actionIndex)
                paths.remove(pointerId)
                invalidate()
            }
        }


        return true
    }
}