This article is a series of articles
with a picture that slides with the finger – single-touch
custom View: multi-touch (1) – finger relay
custom View: multi-touch (2) – multi-finger collaboration
custom View: Multi-touch (3) -- fighting each other
This article is a series of articles. It is recommended to read the previous articles first, otherwise you may not be able to understand the following content
The essence of the collaborative type is to find the center coordinates of all fingers, that is, the focus, and do things around the focus coordinates
Let's first post the heaviest code of the previous article, we need to modify it on the basis of the previous article
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
}
}
How to calculate the center point of the multi-finger, that is, the focus?
For example, the center point of two fingers is the addition of the abscissa of the two points/2, and the addition of the ordinate of the two points/2
What about three o'clock? the same
the same for n points
Then we can write code
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
......
}
The above is the process of calculating a focus. It can be found that after the focus is calculated, the essence is that the View moves with the focus. It doesn’t matter how many fingers there are, so it becomes the original one-finger operation again.
Then we change the finger used by the one-finger logic of the rest of the code to this focus.
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()
}
}
}
The above mainly reflects that the acquisition focus is replaced by a virtual focus
So do you still need to deal with multiple fingers? If it is not processed, when a new finger is pressed, the focus position will be changed instantly, which will cause the picture to move instantaneously, so we still need to deal with multi-finger
But the processing here doesn’t need to be so complicated. No matter how many fingers are pressed or lifted, you only need to do the same thing, which is also the logic of the previous single touch: reset the focus coordinates
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
}
We run the above code test:
- One finger swipe, no problem
- Add a finger, one moves and the other does not move (same direction), the picture moves at half speed, no problem
- Two fingers move together in the same direction, full speed, no problem
- lift a finger, broken, image jumps
I obviously dealt with the lifting of multiple fingers, why is there still a problem? In fact, the problem is in event.pointerCount . As we said before, the number of this acquisition includes the finger that is being lifted, so we say that we lift the finger When, the returned quantity does not correspond to the actual quantity
So let's say we need to handle two places
When one is ACTION_POINTER_UP , we need to return the number of fingers - 1 , and the other is to subtract this offset when calculating the offset in a loop
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
}
test
- One finger swipe, no problem
- Add a finger, one moves and the other does not move (same direction), the picture moves at half speed, no problem
- Two fingers move together in the same direction, full speed, no problem
- lift a finger, no problem
Well, we have finished explaining the type of collaboration here, let’s post all the code
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
}
}