Android multi-touch understanding

First of all, multi-touch needs to use event.getActionMasked() to get events, and the call is as follows:

  • case MotionEvent.ACTION_DOWN: //The first finger press trigger, only trigger once
  • case MotionEvent.ACTION_MOVE: //All finger move events will trigger this event
  • case MotionEvent.ACTION_UP: //Only trigger once, trigger when the last finger is lifted
  • case MotionEvent.ACTION_POINTER_DOWN: //Not triggered by the first finger press
  • case MotionEvent.ACTION_POINTER_UP: //Triggered when the last finger is lifted
    Next, let's look at a piece of code and effect
public class MultiTouchView extends View {
    
    
    private float offsetX, offsetY;
    private float lastOffsetX, lastOffsetY;
    private float downX, downY;
    Bitmap bitmap;
    Paint paint;
    float currentScale;
    private int currentPointId;

    。。。省略

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    
    
        super.onSizeChanged(w, h, oldw, oldh);
        bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.test3);
        paint = new Paint();
        if ((float) bitmap.getWidth() / bitmap.getHeight() > (float) getWidth() / getHeight()) {
    
    //图片是横向图片
            currentScale = (float) getWidth() / bitmap.getWidth();
        } else {
    
    
            currentScale = (float) getHeight() / bitmap.getHeight();
        }
    }

    @Override
    protected void onDraw(Canvas canvas) {
    
    
        super.onDraw(canvas);
        canvas.translate(offsetX, offsetY);
        canvas.scale(currentScale, currentScale, getWidth() / 2f, getHeight() / 2f);
        canvas.drawBitmap(bitmap, (getWidth() - bitmap.getWidth()) / 2f, (getHeight() - bitmap.getHeight()) / 2f, paint);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
    
    
        switch (event.getActionMasked()) {
    
     //getAction
            //只会触发一次
            case MotionEvent.ACTION_DOWN:
                downX = event.getX();
                downY = event.getY();
                currentPointId=0;
                break;
                //所有手指移动都是触发这个事件
            case MotionEvent.ACTION_MOVE:
                //获取id对应的index值,index是会变化的,id不会变化
                //int index= event.findPointerIndex(currentPointId);

                //移动距离:上次偏移值+当前滑动距离
                offsetX = lastOffsetX + event.getX() - downX;
                //event.getY() 使用的是pointerIndex
                offsetY = lastOffsetY + event.getY() - downY;
                invalidate();
                break;
            //只会触发一次,最后一根手指抬起时触发
            case MotionEvent.ACTION_UP:
                //抬手记录上次偏移值
                lastOffsetX = offsetX;
                lastOffsetY = offsetY;
                break;
                //非第一跟手指按下触发
            case MotionEvent.ACTION_POINTER_DOWN:
                break;
                //非最后一根手指抬起触发
            case MotionEvent.ACTION_POINTER_UP:
                break;

        }
        return true;
    }
}

Customized a view, onDraw draws a picture, and offsetX(Y) achieves the effect of sliding the picture with the finger during ACTION_MOVE:

my operation here is

  1. First press finger 1 in the upper right corner, slide, and the picture can slide with the finger
  2. Then press finger 2 in the lower left corner and slide to find that finger 2 cannot slide the picture (still only slides with finger 1)
  3. Then release finger 1, then the picture jumps to the lower left corner and can slide with finger 2
  4. Finally, press finger 1 again, the picture jumps to the upper right corner and slides with finger 1

Why can't I slide the picture by pressing finger 2 in the second step? Look at the code in MotionEvent.ACTION_MOVE:

offsetX = lastOffsetX + event.getX() - downX

The problem lies in this event.getX(). Check the source code of MotionEvent:
insert image description here
the second parameter pointerIndex uses the default value of 0. After some research, it is found that
after each finger is pressed, the system will save the index and id of the finger. After removing one finger, the index will be reordered, and the original id will remain unchanged. When inserting a finger, the insertion operation will be performed according to the id list, as shown in the figure below:
insert image description here
When pressing four fingers in sequence, the id and index of the four fingers are the same, which is 0123 in turn, and when the second finger is removed, the order will be reordered , the index of the third finger becomes 1, the id remains unchanged, the index of the fourth finger becomes 2, and the id remains unchanged. At this time, if you press another finger, it will find that the id is only 0, 2, and 3, so a finger with id 1 is generated, and then reordered to become the same as the initial state.

Looking back at the previous operations, two fingers were generated when pressing fingers 1 and 2 in sequence: finger 1 (id=0, index=0), finger 2 (id=1, index=1). Therefore, in the first operation
above In the second step, because the index of event.getX() in Action_move is always 0, the first finger is processed, and the second finger naturally cannot pull the picture; in the third step, because the first finger is
removed Finger 2 is reordered, and the index of finger 2 becomes 0, so finger 2 can drag the picture
Step 4, when finger 1 is pressed again, it is found that there is only finger 2 (id=1), and there is no id0, so create A finger id is 0, and then reordering becomes finger 1 (id=0, index=0), finger 2 (id=1, index=1), the index of finger 1 is 0, so it is finger 1 Can drag pictures.

Next, practice, change the above View to: the last pressed finger pulls the picture
Idea: record the id of the last pressed finger, get the index according to the id of the finger, and pass event.getX/Y() in ACTION_MOVE to the index:

 case MotionEvent.ACTION_DOWN:
                downX = event.getX();
                downY = event.getY();
                currentPointId=0;
                break;
                //所有手指移动都是触发这个事件
case MotionEvent.ACTION_MOVE:
                //获取id对应的index值,index是会变化的,id不会变化
                int index= event.findPointerIndex(currentPointId);//根据id获取index

                //移动距离:上次偏移值+当前滑动距离
                offsetX = lastOffsetX + event.getX(index) - downX;
                //event.getY() 使用的是pointerIndex
                offsetY = lastOffsetY + event.getY(index) - downY;
                invalidate();
                break;
            //只会触发一次,最后一根手指抬起时触发
 case MotionEvent.ACTION_UP:
                //抬手记录上次偏移值
                lastOffsetX = offsetX;
                lastOffsetY = offsetY;
                break;
                //非第一跟手指按下触发
case MotionEvent.ACTION_POINTER_DOWN:
                int pointerIndex= event.getActionIndex();
                currentPointId=event.getPointerId(pointerIndex);
                //解决跳动
                downX=event.getX(pointerIndex);
                downY=event.getY(pointerIndex);
                lastOffsetX = offsetX;
                lastOffsetY = offsetY;
                break;

The current id is recorded at ACTION_DOWN: currentPointId=0 (because it is the first finger id must be 0), and when ACTION_POINTER_DOWN is not the first finger pressed, get the pressed id through getActionIndex, event.getPointerId(pointerIndex) , and finally find the index of the current id through findPointerIndex(currentPointId) in the ACTION_MOVE event, thus ensuring that the index is the index of the last pressed finger.
At this point, the effect of the last pressed finger pulling the picture is achieved, but a new problem is introduced: when the finger that dragged the picture is lifted up, there will be a flashback.
This is because the event.findPointerIndex(currentPointId) array is out of bounds, because the currentPointId is removed after the finger is lifted, and an error will be reported when finding the index through this id. Processing in ACTION_POINTER_UP:

            //非最后一根手指抬起触发
            case MotionEvent.ACTION_POINTER_UP:
                int upIndex = event.getActionIndex();
                int pointerUpId = event.getPointerId(upIndex);
                if (pointerUpId == currentPointId) {
    
    
                    //如果抬起的是最后一个手指
                    if (upIndex == event.getPointerCount() - 1) {
    
    
                        upIndex = event.getPointerCount() - 2;
                    } else {
    
    
                        upIndex++;
                    }
                    currentPointId = event.getPointerId(upIndex);
                    //记录位置,解决跳动
                    downX = event.getX(upIndex);
                    downY = event.getY(upIndex);
                    lastOffsetX = offsetX;
                    lastOffsetY = offsetY;
                }
                break;

Get the finger id when the finger is lifted. If the id is the id of the finger that is controlling the dragging of the picture, then if the id is the last finger, the index will be rolled back by one (handling the drag to the previous finger), if the id is the last finger If the id is not the last one, then index++ will be handed over to the next finger for processing.
In the end, the problem that the picture jumps when the new finger is pressed is because the current downX (Y) and lastOffsetX (Y) are not recorded when the finger is pressed.
Full code ------------------------------------------------ ---------------------------------------->

 import android.content.Context;
import android.graphics.Bitmap;
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 androidx.annotation.Nullable;

public class MultiTouchView extends View {
    
    
    private float offsetX, offsetY;
    private float lastOffsetX, lastOffsetY;
    private float downX, downY;
    Bitmap bitmap;
    Paint paint;
    float currentScale;
    private int currentPointId;

    public MultiTouchView(Context context) {
    
    
        this(context, null);
    }

    public MultiTouchView(Context context, @Nullable AttributeSet attrs) {
    
    
        this(context, attrs, 0);
    }

    public MultiTouchView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
    
    
        super(context, attrs, defStyleAttr);


    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    
    
        super.onSizeChanged(w, h, oldw, oldh);
        bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.test3);
        paint = new Paint();
        if ((float) bitmap.getWidth() / bitmap.getHeight() > (float) getWidth() / getHeight()) {
    
    //图片是横向图片
            currentScale = (float) getWidth() / bitmap.getWidth();
        } else {
    
    
            currentScale = (float) getHeight() / bitmap.getHeight();

        }
    }

    @Override
    protected void onDraw(Canvas canvas) {
    
    
        super.onDraw(canvas);
        canvas.translate(offsetX, offsetY);
        canvas.scale(currentScale, currentScale, getWidth() / 2f, getHeight() / 2f);
        canvas.drawBitmap(bitmap, (getWidth() - bitmap.getWidth()) / 2f, (getHeight() - bitmap.getHeight()) / 2f, paint);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
    
    
        switch (event.getActionMasked()) {
    
     //getAction
            //只会触发一次
            case MotionEvent.ACTION_DOWN:
                downX = event.getX();
                downY = event.getY();
                currentPointId = 0;
                break;

            //所有手指移动都是触发这个事件
            case MotionEvent.ACTION_MOVE:
                //获取id对应的index值,index是会变化的,id不会变化
                int index = event.findPointerIndex(currentPointId);//根据id获取index

                //移动距离:上次偏移值+当前滑动距离
                offsetX = lastOffsetX + event.getX(index) - downX;
                //event.getY() 使用的是pointerIndex
                offsetY = lastOffsetY + event.getY(index) - downY;
                invalidate();
                break;

            //只会触发一次,最后一根手指抬起时触发
            case MotionEvent.ACTION_UP:
                //抬手记录上次偏移值
                lastOffsetX = offsetX;
                lastOffsetY = offsetY;
                break;

            //非第一跟手指按下触发
            case MotionEvent.ACTION_POINTER_DOWN:
                int pointerIndex = event.getActionIndex();
                currentPointId = event.getPointerId(pointerIndex);
                //记录位置,解决跳动
                downX = event.getX(pointerIndex);
                downY = event.getY(pointerIndex);
                lastOffsetX = offsetX;
                lastOffsetY = offsetY;
                break;
            //非最后一根手指抬起触发

            case MotionEvent.ACTION_POINTER_UP:
                int upIndex = event.getActionIndex();
                int pointerUpId = event.getPointerId(upIndex);
                if (pointerUpId == currentPointId) {
    
    
                    //如果抬起的是最后一个手指
                    if (upIndex == event.getPointerCount() - 1) {
    
    
                        upIndex = event.getPointerCount() - 2;
                    } else {
    
    
                        upIndex++;
                    }
                    currentPointId = event.getPointerId(upIndex);
                    //记录位置,解决跳动
                    downX = event.getX(upIndex);
                    downY = event.getY(upIndex);
                    lastOffsetX = offsetX;
                    lastOffsetY = offsetY;
                }

                break;

        }
        return true;
    }
}

final effect:

Guess you like

Origin blog.csdn.net/weixin_40652755/article/details/127349899