preface
In the previous blog, we made a picture view that can move with our fingers. It can only support single finger control. If we touch with multiple fingers, it will be all right. We want the effect
There are three types of multi touch
① : baton type or preemptive type
② : Collaborative
③ : non-interference type
1, Baton type or preemptive type
thinking
① : confirm a touch point as a special touch point for controlling the picture
② : when moving, the contact with picture control is the only contact
③ : if a second finger is pressed, control is transferred from the first finger to the second finger
④ : if there are multiple fingers and one of them is lifted, then if the lifted finger has control over the picture, the control will be exchanged
code
1: Action_ When down, when only one finger is pressed, the image control right is assigned to the only finger with index 0
MotionEvent.ACTION_DOWN -> { leadingPointerId = event.getPointerId(0) downX = event.x downY = event.y originOffsetX = offsetX originOffsetY = offsetY }
2: Action_ POINTER_ When down, give control of the picture to the new finger
//Relay, give the control of the picture to the finger just clicked MotionEvent.ACTION_POINTER_DOWN -> { val actionIndex = event.actionIndex leadingPointerId = event.getPointerId(actionIndex) downX = event.getX(actionIndex) downY = event.getY(actionIndex) originOffsetX = offsetX originOffsetY = offsetY }
3: A finger is raised
//Relay, if the removed finger is the finger that has control, give control to the other fingers MotionEvent.ACTION_POINTER_UP -> { val isLeadingPointer = event.findPointerIndex(leadingPointerId) == event.actionIndex if (isLeadingPointer) { val newIndex = if (event.findPointerIndex(leadingPointerId) == event.pointerCount - 1) { event.pointerCount - 2 } else { event.pointerCount - 1 } leadingPointerId = event.getPointerId(newIndex) downX = event.getX(newIndex) downY = event.getY(newIndex) originOffsetX = offsetX originOffsetY = offsetY } }
1: If the raised finger has no control over the picture, it will not be processed
2: If the raised finger has control of the picture
① : if the raised finger is the last one, give control to the penultimate one
② : otherwise, give it to the penultimate one
3: The values downX, originOffsetY are updated only during relay
Complete code
package com.lbj23.customview.customview 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.lbj23.customview.R import com.lbj23.customview.dp import com.lbj23.customview.getAvatar class MultiTouchView(context: Context?, attrs: AttributeSet?) : View(context, attrs) { private val image = getAvatar(resources, R.drawable.bg6, 85.dp.toInt()) private var offsetX = 0f private var offsetY = 0f private var originOffsetX = 0f private var originOffsetY = 0f private var downX = 0f private var downY = 0f private val paint = Paint(Paint.ANTI_ALIAS_FLAG) private var leadingPointerId = 0 override fun onDraw(canvas: Canvas) { super.onDraw(canvas) canvas.drawBitmap(image, offsetX, offsetY, paint) } override fun onTouchEvent(event: MotionEvent): Boolean { when (event.actionMasked) { MotionEvent.ACTION_DOWN -> { leadingPointerId = event.getPointerId(0) downX = event.x downY = event.y originOffsetX = offsetX originOffsetY = offsetY } //Relay, if the removed finger is the finger that has control, give control to the other fingers MotionEvent.ACTION_POINTER_UP -> { val isLeadingPointer = event.findPointerIndex(leadingPointerId) == event.actionIndex if (isLeadingPointer) { val newIndex = if (event.findPointerIndex(leadingPointerId) == event.pointerCount - 1) { event.pointerCount - 2 } else { event.pointerCount - 1 } leadingPointerId = event.getPointerId(newIndex) downX = event.getX(newIndex) downY = event.getY(newIndex) originOffsetX = offsetX originOffsetY = offsetY } } //Relay, give the control of the picture to the finger just clicked MotionEvent.ACTION_POINTER_DOWN -> { val actionIndex = event.actionIndex leadingPointerId = event.getPointerId(actionIndex) downX = event.getX(actionIndex) downY = event.getY(actionIndex) originOffsetX = offsetX originOffsetY = offsetY } MotionEvent.ACTION_MOVE -> { val index = event.findPointerIndex(leadingPointerId) offsetX = event.getX(index) - downX + originOffsetX offsetY = event.getY(index) - downY + originOffsetY invalidate() } } return true } }
Effect display
Obviously, only one finger has control over the picture
2, Collaborative
thinking
① : when one finger, the picture is controlled by only one finger
② : when multiple fingers are pressed on the screen at the same time, the geometric center of multiple fingers is used as a virtual finger to control the picture
code
1: In onTouchEvent, before judging various events, first find the geometric center and find the X and Y coordinates of the virtual finger with picture control
var sumX = 0f var sumY = 0f val isPointerUP = event.actionMasked == MotionEvent.ACTION_POINTER_UP for (index in 0 until event.pointerCount) { //If you are lifting, you need to do the operation in advance. At this time, the pointerCount still does not subtract the number of points raised, so update the position in advance if (!(isPointerUP && index == event.actionIndex)) { sumX += event.getX(index) sumY += event.getY(index) } } var pointerCount = event.pointerCount if (isPointerUP) { pointerCount-- } //Set a geometric center val focusX = sumX / pointerCount val focusY = sumY / pointerCount
① : use sumX and sumY to record the total coordinates by traversing all fingers
② : divide sumX and sumY by the number of fingers at the same time to get the position of the geometric center
③ : if event.actionMasked is MotionEvent.ACTION_POINTER_UP, because all fingers are traversed at this time, the raised finger is still there. There will be problems when using this geometric center in subsequent actions, so if it is MotionEvent.ACTION_POINTER_UP, then peel the raised finger from the calculation of the geometric center
2: If you click and lift, update the click position and the original position
MotionEvent.ACTION_DOWN, MotionEvent.ACTION_POINTER_UP, MotionEvent.ACTION_POINTER_DOWN -> { downX = focusX downY = focusY originOffsetX = offsetX originOffsetY = offsetY }
3: If it is moving, change the image offset and use the position of the geometric center
MotionEvent.ACTION_MOVE -> { offsetX = focusX - downX + originOffsetX offsetY = focusY - downY + originOffsetY invalidate() }
Effect display
It is observed that:
① : when two fingers slide, the picture drag speed is faster
② : one finger is fixed and the other finger slides. The picture drag speed is slow, which is half the speed of the moving finger
Complete code
package com.lbj23.customview.customview 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.lbj23.customview.R import com.lbj23.customview.dp import com.lbj23.customview.getAvatar class MultiTouchView2(context: Context?, attrs: AttributeSet?) : View(context, attrs) { private val image = getAvatar(resources, R.drawable.bg6, 150.dp.toInt()) private var offsetX = 0f private var offsetY = 0f private var originOffsetX = 0f private var originOffsetY = 0f private var downX = 0f private var downY = 0f private val paint = Paint(Paint.ANTI_ALIAS_FLAG) override fun onDraw(canvas: Canvas) { super.onDraw(canvas) canvas.drawBitmap(image, offsetX, offsetY, paint) } override fun onTouchEvent(event: MotionEvent): Boolean { var sumX = 0f var sumY = 0f val isPointerUP = event.actionMasked == MotionEvent.ACTION_POINTER_UP for (index in 0 until event.pointerCount) { //If you are lifting, you need to do the operation in advance. At this time, the pointerCount still does not subtract the number of points raised, so update the position in advance if (!(isPointerUP && index == event.actionIndex)) { sumX += event.getX(index) sumY += event.getY(index) } } var pointerCount = event.pointerCount if (isPointerUP) { pointerCount-- } //Set a geometric center val focusX = sumX / pointerCount val focusY = sumY / pointerCount when (event.actionMasked) { //If you click and lift, update the click position and the original position MotionEvent.ACTION_DOWN, MotionEvent.ACTION_POINTER_UP, MotionEvent.ACTION_POINTER_DOWN -> { downX = focusX downY = focusY originOffsetX = offsetX originOffsetY = offsetY } //If it is moving, change the position and use the geometric center MotionEvent.ACTION_MOVE -> { offsetX = focusX - downX + originOffsetX offsetY = focusY - downY + originOffsetY invalidate() } } return true } }
4: Each fighting his own way, each painting his own way
This type requires us to treat each finger separately
Click Details