【朝花夕拾】Android自定义View篇之(九)多点触控(下)实践出真知
前言
转载请声明,转自【https://www.cnblogs.com/andy-songwei/p/11158972.html】谢谢!
在上一篇文章中,已经总结了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 @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的布局如下:
复制代码
1
2
5
6
9
复制代码
用一根手指在图片上进行拖动,效果如下左图所示,图片随着手指在滑动:
效果很完美,但上述代码存在一个问题,就是在单指操作的情况下,可以正常被拖动,但是如果是多指操作的时候,就会混乱了。右图为两根手指滑动的图片的效果,因为两根手指都在移动, 导致ACTION_MOVE事件中,一会儿以第一根手指的触摸点为坐标,一会儿又以第二根手指的触摸点为坐标,这就导致图片频繁跳跃。
二、实现多指操作时只有第一根手指可以拖动图片
这一节我们在上述代码基础上,实现第一根手指在拖动图片时,另一根手指继续按下并拖动时无效,也就是第二根手指无法拖动,对第一根手指没有干扰。由于是多点触控,需要使用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,因为获取指定手指事件坐标的函数传入的参数都是它。结合代码中的注释,剩下的逻辑应该就比较容易看懂了。
效果图如下,用两根手指来依次按下并拖动图片:
我们发现,只有第一根手指在滑动时,图片才会跟着移动,第二根手指(右边的手指)的滑动无效,完美!!!
三、实现两根手指共同拖动图片
上面实现的效果还不够,用户在使用中,第二根手指滑动时也能接替第一根手指继续滑动。基本思路大致是,记录当前活动手指的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、54、65行所示。依然用两根手指依次拖动图片,效果如下所示:
现在可以看到,两根手指轮流正常拖动图片了,毫无违和感。
结语
到目前为止,多点触控相关的内容,我想讲的已经讲完了,上一篇讲理论,这一篇讲案例,难点其实主要就是pointerIndex和pointerId的理解和使用。希望通过这两篇文章,对读者理解多点触控有所帮助。由于文中代码结构比较简单,就没有必要提供源码了,读者自己建立好项目,把这些代码依次拷贝过去就可以了,非常简单。还有就是笔者比较穷,使用的免费软件,所以文中gif图上都打了水印,以后挣钱了也去享受一下付费服务,把水印给去掉。文中如果有描述不准确或不妥的地方,欢迎来拍砖,万分感谢,Bye!!!https://www.cnblogs.com/andy-songwei/p/11158972.html