【朝花夕拾】Android自定义View篇之(九)多点触控(下)实践出真知——实现多指拖动图片

前言css

       转载请声明,转自【https://www.cnblogs.com/andy-songwei/p/11158972.html】谢谢!html

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

       本文的主要内容以下:canvas

 

1、实现单指拖动图片ide

       要实现单指拖动图片,大体思路就是监控手指的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  @Override 24     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  @Override 54     protected void onDraw(Canvas canvas) { 55         super.onDraw(canvas); 56  canvas.drawBitmap(mBitmap, mMatrix, mPaint); 57  } 58 }

代码逻辑比较简单,关键处也有这注释说明,这里就很少说了。使用该自定义View的布局以下:oop

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.SingleTouchDragView 7         android:layout_width="match_parent"
8  android:layout_height="match_parent" />
9 </RelativeLayout>

用一根手指在图片上进行拖动,效果以下左图所示,图片随着手指在滑动:布局

                

       效果很完美,但上述代码存在一个问题,就是在单指操做的状况下,能够正常被拖动,可是若是是多指操做的时候,就会混乱了。右图为两根手指滑动的图片的效果,由于两根手指都在移动, 致使ACTION_MOVE事件中,一下子以第一根手指的触摸点为坐标,一下子又以第二根手指的触摸点为坐标,这就致使图片频繁跳跃。post

 

2、实现多指操做时只有第一根手指能够拖动图片学习

       这一节咱们在上述代码基础上,实现第一根手指在拖动图片时,另外一根手指继续按下并拖动时无效,也就是第二根手指没法拖动,对第一根手指没有干扰。因为是多点触控,须要使用getActionMasked()来获取事件,并监听ACTION_POINTER_DOWN和ACTION_POINTER_UP事件。

 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  @Override 24     public boolean onTouchEvent(MotionEvent event) { 25         switch (event.getActionMasked()) { 26             case MotionEvent.ACTION_DOWN: 27             case MotionEvent.ACTION_POINTER_DOWN: 28                 //pointerId为0的手指(即咱们定义的第一根手指)按下在指定区域内
29                 if (event.getPointerId(event.getActionIndex()) == 0 && mRectF.contains(event.getX(), event.getY())) { 30                     mCanDrag = true; 31                     //getX()和getY()没有传入参数时,默认传入的0
32  mLstPointF.set(event.getX(), event.getY()); 33  } 34                 break; 35             case MotionEvent.ACTION_MOVE: 36                 if (mCanDrag) { 37                     int pointerIndex = event.findPointerIndex(0);//第一根手指的pointerId为0 38                     //这里须要注意,多手指频繁按下和抬起时可能会出现pointerIndex为-1的状况,如不处理,后面会报错
39                     if (pointerIndex == -1) { 40                         break; 41  } 42                     mMatrix.postTranslate(event.getX(pointerIndex) - mLstPointF.x, 43                                              event.getY(pointerIndex) - mLstPointF.y); 44  mLstPointF.set(event.getX(pointerIndex), event.getY(pointerIndex)); 45                     mRectF = new RectF(0, 0, mBitmap.getWidth(), mBitmap.getHeight()); 46  mMatrix.mapRect(mRectF); 47  invalidate(); 48  } 49                 break; 50             case MotionEvent.ACTION_POINTER_UP: 51             case MotionEvent.ACTION_UP: 52                 if (event.getPointerId(event.getActionIndex()) == 0) { 53                     mCanDrag = false; 54  } 55                 break; 56  } 57         return true; 58  } 59 
60  @Override 61     protected void onDraw(Canvas canvas) { 62         super.onDraw(canvas); 63  canvas.drawBitmap(mBitmap, mMatrix, mPaint); 64  } 65 }

       因为咱们要实现的效果是只有第一根手指能够拖动图片,因此在第29行和52行中,根据pointerId是否为0来判断是否须要更新界面。上一篇文章中说过,在处理多点触控事件时,要用pointerId来跟踪手指事件。因为第一根手指的pointerId为0,因此经过pointerId是否为0来判断是否为第一根手指。当有多根手指在屏幕上时,第一根手指抬起再按下,它仍然被认为是第一跟手指,此时触发的是ACTION_POINTER_DOWN事件,因此第一根手指按下,ACTION_POINTER_DOWN和ACTION_DOWN都有可能触发。若是判断是第一根手指按下了,就记录下它按下时的坐标,并设置mCanDrag为true,表示能够滑动。而手指抬起时,多是最后一根抬起的手指,也可能不是,因此ACTION_POINTER_UP和ACTION_UP也均可能触发。若是检测到第一根手指抬起了,就设置mCanDrag为false,表示图片不可以再滑动了。在ACTION_MOVE事件中,第37行是固定使用,都须要根据findPointerIndex(int pinterId)来获得pointerIndex,由于获取指定手指事件坐标的函数传入的参数都是它。结合代码中的注释,剩下的逻辑应该就比较容易看懂了。

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

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

 

3、实现两根手指共同拖动图片

       上面实现的效果还不够,用户在使用中,第二根手指滑动时也能接替第一根手指继续滑动。基本思路大体是,记录当前活动手指的pointerId,ACTION_MOVE中以活动手指为基础来肯定滑动操做。仍然在上述代码基础上修改来实现。

 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  @Override 26     public boolean onTouchEvent(MotionEvent event) { 27         int actionIndex = event.getActionIndex(); 28         switch (event.getActionMasked()) { 29             case MotionEvent.ACTION_DOWN: 30                 //getX()和getY()没有传入参数时,默认传入的0
31                 if (mRectF.contains(event.getX(), event.getY())) { 32                     mActivePointerId = 0; //第一根手指按下时,pointerId和pointerIndex都为0
33                     mCanDrag = true; 34  mLstPointF.set(event.getX(), event.getY()); 35  } 36                 break; 37             case MotionEvent.ACTION_POINTER_DOWN: 38                 //有新落下的手指,则将新落下的手指做为活动手指,保存下活动手指的坐标
39                 mActivePointerId = event.getPointerId(actionIndex); 40  mLstPointF.set(event.getX(actionIndex), event.getY(actionIndex)); 41                 break; 42             case MotionEvent.ACTION_MOVE: 43                 if (mActivePointerId == INVALID_POINTER) { 44                     break; 45  } 46                 if (mCanDrag) {
47 //这里根据活动手指的pointerId来找到pointerIndex,而再也不是固定的手指的pointerId了
48 int pointerIndex = event.findPointerIndex(mActivePointerId); 49 if (pointerIndex == -1) { 50 break; 51 } 52 mMatrix.postTranslate(event.getX(pointerIndex) - mLstPointF.x, 53 event.getY(pointerIndex) - mLstPointF.y); 54 mLstPointF.set(event.getX(pointerIndex), event.getY(pointerIndex)); 55 mRectF = new RectF(0, 0, mBitmap.getWidth(), mBitmap.getHeight()); 56 mMatrix.mapRect(mRectF); 57 invalidate(); 58 } 59 break; 60 case MotionEvent.ACTION_POINTER_UP: 61 //若是当前抬起的手指为活动手指,那么活动手指就传给留下的手指中pointerIndex最前面的一个 62 if (mActivePointerId == event.getPointerId(actionIndex)) { 63 int newPointerIndex = actionIndex == 0 ? 1 : 0; 64 mActivePointerId = event.getPointerId(newPointerIndex); 65 mLstPointF.set(event.getX(newPointerIndex), event.getY(newPointerIndex)); 66 } 67 break; 68 case MotionEvent.ACTION_UP: 69 //最后一根手指也抬起来了 70 mActivePointerId = INVALID_POINTER; 71 mCanDrag = false; 72 break; 73 } 74 return true; 75 } 76 77 @Override 78 protected void onDraw(Canvas canvas) { 79 super.onDraw(canvas); 80 canvas.drawBitmap(mBitmap, mMatrix, mPaint); 81 } 82 }

      因为须要依据活动的手指来拖动图片,因此须要实时记录下活动手指的坐标,如第40、5四、65行所示。依然用两根手指依次拖动图片,效果以下所示:

如今能够看到,两根手指轮流正常拖动图片了,毫无违和感。

 

结语

      到目前为止,多点触控相关的内容,我想讲的已经讲完了,上一篇讲理论,这一篇讲案例,难点其实主要就是pointerIndex和pointerId的理解和使用。但愿经过这两篇文章,对读者理解多点触控有所帮助。因为文中代码结构比较简单,就没有必要提供源码了,读者本身创建好项目,把这些代码依次拷贝过去就能够了,很是简单。还有就是笔者比较穷,使用的免费软件,因此文中gif图上都打了水印,之后挣钱了也去享受一下付费服务,把水印给去掉。文中若是有描述不许确或不妥的地方,欢迎来拍砖,万分感谢,Bye!!!

 

参考文章

       【Android多点触控最佳实践

      【安卓自定义View进阶-多点触控详解

原文出处:https://www.cnblogs.com/andy-songwei/p/11158972.html

相关文章
相关标签/搜索