首先多点触控要使用event.getActionMasked()来获取事件,调用情况如下:
- case MotionEvent.ACTION_DOWN: //第一根手指按下触发,只会触发一次
- case MotionEvent.ACTION_MOVE: //所有手指的move事件都会触发这个事件
- case MotionEvent.ACTION_UP: //只会触发一次,最后一根手指抬起时触发
- case MotionEvent.ACTION_POINTER_DOWN: //非第一跟手指按下触发
- case MotionEvent.ACTION_POINTER_UP: //非最后一根手指抬起触发
接下来看一段代码和效果
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;。。。省略@Overrideprotected 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();}}@Overrideprotected 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);}@Overridepublic 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() 使用的是pointerIndexoffsetY = 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;}
}
自定义了一个view,onDraw绘制了一张图片,在ACTION_MOVE的时候offsetX(Y)达到图片随手指滑动的效果:
我这里的操作是
- 先在右上角按下手指1,滑动,图片能跟随手指滑动
- 接着在左下角按下手指2并滑动发现手指2没法滑动图片(依旧只会跟随手指1滑动)
- 然后松开手指1,这时候图片跳到左下角并能跟随手指2滑动了
- 最后再按下手指1,图片跳到了右上角并跟随手指1滑动
为什么第二步按下手指2没法滑动图片呢?来看MotionEvent.ACTION_MOVE中的代码:
offsetX = lastOffsetX + event.getX() - downX
问题就在这个event.getX(),查看MotionEvent 的源码:
第二个参数pointerIndex用的是默认值0,一番查阅后发现:
每根手指按下后系统都会保存该手指的index和id,移除一根手指后index会重新排序,原id不变。当插入一个手指时会根据id列表进行插入操作,如 下图:
当依次按下四根手指时四根手指的id和index相同,依次是0123,当移除第二根手指,那么将会重新排序,第三根手指的index变为1,id不变,第四根手指的index变为2,id不变。这时候再按下一个手指它会发现id只有0、2、3,于是生成了id为1的手指,然后重新排序变成跟最开始的状态一样。
回头看之前的操作,依次按下手指1和2时生成了两个:手指1(id=0,index=0),手指2(id=1,index=1)
因此,在上面的操作的第二步中,因为Action_move中event.getX() 始终用的index都是0,所以处理的都是第一根手指,第二根手指自然无法拉动图片;
第三步中,因为移除了第一根手指,进行了重新排序,手指2的index变成了0,所以手指2能拖动图片
第四步,手指1重新按下发现当前只有手指2(id=1),并没有id0,因此创建了一个手指id为0,再重新排序的时候变成了手指1(id=0,index=0),手指2(id=1,index=1),手指1的index为0,因此是手指1能拖动图片。
接下来实践一下,将上面的View改为:最后按下的手指拉动图片
思路:记录最后按下手指的id,根据手指的id获取index,ACTION_MOVE中event.getX/Y()传入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() 使用的是pointerIndexoffsetY = 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;
在ACTION_DOWN时记录了当前id:currentPointId=0(因为是第一个手指id必然是0),在ACTION_POINTER_DOWN即非第一根手指按下的时候通过getActionIndex、event.getPointerId(pointerIndex)得到按下的id,最后在ACTION_MOVE事件中通过findPointerIndex(currentPointId)找到当前id的index,这样就保证了index为最后按下的手指index。
到此就实现了最后按下的手指拉动图片的效果,但是引入了新的问题:当拖动图片的那根手指抬起来后会出现闪退。
这是因为event.findPointerIndex(currentPointId)数组越界,因为手指抬起后currentPointId被移除了,通过这个id找index自然会报错。在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;
当手指抬起时获取手指id,如果id是正在控制拖动图片的手指id,那么如果该id是最后一个手指则将index回退一个(将拖动交给上一根手指处理),如果该id不是最后一个,那么index++,即交给下一根手指处理。
最后剩下新手指按下图片跳动的问题是因为手指按下时没有记录当前的downX(Y)和lastOffsetX (Y)。
完整代码---------------------------------------------------------------------------------------->
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);}@Overrideprotected 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();}}@Overrideprotected 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);}@Overridepublic 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() 使用的是pointerIndexoffsetY = 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;}
}
最终效果: