【朝花夕拾】Android自定义View篇之(九)多点触控(下)实践出真知

前言

       在上一篇文章中,已经总结了MotionEvent以及多点触控相关的基础理论知识和常用的函数。本篇将通过实现单指拖动图片,多指拖动图片的实际案例来进行练习并实现一些效果,来理解前面的理论知识。要理解本文的代码,需要先掌握上一篇的理论知识,事件处理基础,以及一定的自定义View基础,这些我也在本系列文章的前几篇中讲过,有兴趣的可以按照本系列的顺序依次阅读学习,相信您一定会有不小的收获。。

 

一、实现单指拖动图片

       要实现单指拖动图片,大致思路就是监控手指的ACTION_MOVE事件。手指移动过程中,获取事件的坐标,让图片根据坐标的变化来进行移动。

代码实现如下,先自定义一个View,在其中处理单指拖动逻辑。

 1 public class SingleTouchDragView extends View { 2 private static final String TAG = "songzheweiwang"; 3 private Bitmap mBitmap; 4 private RectF mRectF; 5 private Matrix mMatrix; 6 private Paint mPaint; 7 private PointF mLstPointF; 8 private boolean mCanDrag = false; 9 10 public SingleTouchDragView(Context context, @Nullable AttributeSet attrs) {11 super(context, attrs);12  init();13  }14 15 private void init() {16 mPaint = new Paint();17 mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.dog);18 mRectF = new RectF(0, 0, mBitmap.getWidth(), mBitmap.getHeight());19 mMatrix = new Matrix();20 mLstPointF = new PointF();21  }22 23  @Override24 public boolean onTouchEvent(MotionEvent event) {25 switch (event.getAction()) {26 case MotionEvent.ACTION_DOWN:27 //判断按下位置是否在图片区域内28 if (mRectF.contains(event.getX(), event.getY())) {29 mCanDrag = true;30  mLstPointF.set(event.getX(), event.getY());31  }32 break;33 case MotionEvent.ACTION_UP:34 mCanDrag = false;35 case MotionEvent.ACTION_MOVE:36 if (mCanDrag) {37 //移动图片38 mMatrix.postTranslate(event.getX() - mLstPointF.x, event.getY() - mLstPointF.y);39 //更新触摸位置40  mLstPointF.set(event.getX(), event.getY());41 // 更新图片区域42 mRectF = new RectF(0, 0, mBitmap.getWidth(), mBitmap.getHeight());43  mMatrix.mapRect(mRectF);44 //刷新45  invalidate();46  }47 break;48  }49 //注意这里需要返回true,因为当前自定义view继承自基类View,默认是无法消费触摸事件的50 return true;51  }52 53  @Override54 protected void onDraw(Canvas canvas) {55 super.onDraw(canvas);56  canvas.drawBitmap(mBitmap, mMatrix, mPaint);57  }58 }

自定义View使用的布局如下:

1 <?xml version="1.0" encoding="utf-8"?>2 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"3  android:layout_width="match_parent"4  android:layout_height="match_parent">5 6 <com.example.demos.customviewdemo.SingleTouchDragView7 android:layout_width="match_parent"8  android:layout_height="match_parent" />9 </RelativeLayout>

用一根手指在图片上进行拖动,效果如下左图所示:

技术图片                技术图片

       在单指操作的情况下,可以正常被拖动。但是如果是多指操作的时候,就会混乱了。右图为两根手指滑动的图片的效果,因为两根手指都在移动, 导致ACTION_MOVE事件中,一会儿以第一根手指的触摸点为坐标,一会儿又以第二根手指的触摸点为坐标,这就导致图片闪动。

 

二、实现多指操作时只有第一根手指可以拖动图片

 1 public class MultiTouchDragView extends View { 2 private static final String TAG = "songzheweiwang"; 3 private Bitmap mBitmap; 4 private RectF mRectF; 5 private Matrix mMatrix; 6 private Paint mPaint; 7 private PointF mLstPointF; 8 private boolean mCanDrag = false; 9 10 public MultiTouchDragView(Context context, @Nullable AttributeSet attrs) {11 super(context, attrs);12  init();13  }14 15 private void init() {16 mPaint = new Paint();17 mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.dog);18 mRectF = new RectF(0, 0, mBitmap.getWidth(), mBitmap.getHeight());19 mMatrix = new Matrix();20 mLstPointF = new PointF();21  }22 23 @RequiresApi(api = Build.VERSION_CODES.KITKAT)24  @Override25 public boolean onTouchEvent(MotionEvent event) {26 Log.i(TAG, "" + MotionEvent.actionToString(event.getAction()) + ";mCanDrag=" + mCanDrag + ";actionIndex=" + event.getActionIndex() + ";action=" + event.getAction());27 switch (event.getActionMasked()) {28 case MotionEvent.ACTION_DOWN:29 case MotionEvent.ACTION_POINTER_DOWN:30 //pointerId为0的手指(即我们定义的第一根手指)按下在指定区域内31 if (event.getPointerId(event.getActionIndex()) == 0 && mRectF.contains(event.getX(), event.getY())) {32 mCanDrag = true;33 //getX()和getY()没有传入参数时,默认传入的034  mLstPointF.set(event.getX(), event.getY());35  }36 break;37 case MotionEvent.ACTION_MOVE:38 if (mCanDrag) {39 int pointerIndex = event.findPointerIndex(0);40 //这里需要注意,多手指频繁按下和抬起时可能会出现pointerIndex为-1的情况,如不处理,后面会报错41 if (pointerIndex == -1) {42 break;43  }44 mMatrix.postTranslate(event.getX(pointerIndex) - mLstPointF.x, event.getY(pointerIndex) - mLstPointF.y);45  mLstPointF.set(event.getX(), event.getY());46 mRectF = new RectF(0, 0, mBitmap.getWidth(), mBitmap.getHeight());47  mMatrix.mapRect(mRectF);48  invalidate();49  }50 break;51 case MotionEvent.ACTION_POINTER_UP:52 case MotionEvent.ACTION_UP:53 if (event.getPointerId(event.getActionIndex()) == 0) {54 mCanDrag = false;55  }56 break;57  }58 return true;59  }60 61  @Override62 protected void onDraw(Canvas canvas) {63 super.onDraw(canvas);64  canvas.drawBitmap(mBitmap, mMatrix, mPaint);65  }66 }

效果图如下,用两根手指来依次按下并拖动图片:

技术图片

我们发现,只有第一根手指在滑动时,图片才会跟着移动,第二根手指(右边的手指)的滑动无效。

 

三、实现两根手指轮流拖动图片

 1 public class MultiTouchDragView2 extends View { 2 private static final String TAG = "songzheweiwang"; 3 private Bitmap mBitmap; 4 private RectF mRectF; 5 private Matrix mMatrix; 6 private Paint mPaint; 7 private PointF mLstPointF; 8 private boolean mCanDrag = false; 9 private int mActivePointerId;10 private final int INVALID_POINTER = -1;11 12 public MultiTouchDragView2(Context context, @Nullable AttributeSet attrs) {13 super(context, attrs);14  init();15  }16 17 private void init() {18 mPaint = new Paint();19 mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.dog);20 mRectF = new RectF(0, 0, mBitmap.getWidth(), mBitmap.getHeight());21 mMatrix = new Matrix();22 mLstPointF = new PointF();23  }24 25 @RequiresApi(api = Build.VERSION_CODES.KITKAT)26  @Override27 public boolean onTouchEvent(MotionEvent event) {28 Log.i(TAG, "" + MotionEvent.actionToString(event.getAction()) + ";mCanDrag=" + mCanDrag + ";actionIndex=" + event.getActionIndex() + ";action=" + event.getAction());29 int actionIndex = event.getActionIndex();30 switch (event.getActionMasked()) {31 case MotionEvent.ACTION_DOWN:32 //getX()和getY()没有传入参数时,默认传入的033 if (mRectF.contains(event.getX(), event.getY())) {34 mActivePointerId = 0; //第一根手指按下时,pointerId和pointerIndex都为035 mCanDrag = true;36  mLstPointF.set(event.getX(), event.getY());37  }38 break;39 case MotionEvent.ACTION_POINTER_DOWN:40 //有新落下的手指,则将新落下的手指作为活动手指41 mActivePointerId = event.getPointerId(actionIndex);42  mLstPointF.set(event.getX(actionIndex), event.getY(actionIndex));43 break;44 case MotionEvent.ACTION_MOVE:45 if (mActivePointerId == INVALID_POINTER) {46 break;47  }48 if (mCanDrag) {49 int pointerIndex = event.findPointerIndex(mActivePointerId);50 //这里需要注意,多手指频繁按下和抬起时可能会出现pointerIndex为-1的情况,如不处理,后面会报错51 if (pointerIndex == -1) {52 break;53  }54 mMatrix.postTranslate(event.getX(pointerIndex) - mLstPointF.x, event.getY(pointerIndex) - mLstPointF.y);55  mLstPointF.set(event.getX(pointerIndex), event.getY(pointerIndex));56 mRectF = new RectF(0, 0, mBitmap.getWidth(), mBitmap.getHeight());57  mMatrix.mapRect(mRectF);58  invalidate();59  }60 break;61 case MotionEvent.ACTION_POINTER_UP:62 //如果当前抬起的手指为活动手指63 if (mActivePointerId == event.getPointerId(actionIndex)) {64 int newPointerIndex = actionIndex == 0 ? 1 : 0;65 mActivePointerId = event.getPointerId(newPointerIndex);66  mLstPointF.set(event.getX(newPointerIndex), event.getY(newPointerIndex));67  }68 break;69 case MotionEvent.ACTION_UP:70 //最后一根手指也抬起来了71 mActivePointerId = INVALID_POINTER;72 mCanDrag = false;73 break;74  }75 return true;76  }77 78  @Override79 protected void onDraw(Canvas canvas) {80 super.onDraw(canvas);81  canvas.drawBitmap(mBitmap, mMatrix, mPaint);82  }83 }

依然用两根手指依次拖动图片,效果如下所示:

技术图片

现在可以看到,两根手指正常拖动图片了,毫无违和感。

相关文章