【朝花夕拾】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     @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 <?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事件中,一会儿以第一根手指的触摸点为坐标,一会儿又以第二根手指的触摸点为坐标,这就导致图片闪动。

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

 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     @Override
25     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()没有传入参数时,默认传入的0
34                     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     @Override
62     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     @Override
27     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()没有传入参数时,默认传入的0
33                 if (mRectF.contains(event.getX(), event.getY())) {
34                     mActivePointerId = 0; //第一根手指按下时,pointerId和pointerIndex都为0
35                     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     @Override
79     protected void onDraw(Canvas canvas) {
80         super.onDraw(canvas);
81         canvas.drawBitmap(mBitmap, mMatrix, mPaint);
82     }
83 }

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

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

原文地址:https://www.cnblogs.com/andy-songwei/p/11158972.html

时间: 2024-10-07 20:13:00

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

【朝花夕拾】Android自定义View篇之(八)多点触控(上)基础知识

前言 转载请声明,转自[https://www.cnblogs.com/andy-songwei/p/11155259.html],谢谢! 在前面的文章中,介绍了不少触摸相关的知识,但都是基于单点触控的,即一次只用一根手指.但是在实际使用App中,常常是多根手指同时操作,这就需要用到多点触控相关的知识了.多点触控是在Android2.0开始引入的,在现在使用的Android手机上都是支持多点触控的.本系列文章将对常见的多点触控相关的重点知识进行总结,并使用多点触控来实现一些常见的效果,从而达到将

【朝花夕拾】Android自定义View篇之(六)Android事件分发机制(中)从源码分析事件分发逻辑及经常遇到的一些“诡异”现象

前言 转载请注明,转自[https://www.cnblogs.com/andy-songwei/p/11039252.html]谢谢! 在上一篇文章[[朝花夕拾]Android自定义View篇之(五)Android事件分发机制(上)Touch三个重要方法的处理逻辑][下文简称(五),请先阅读完(五)再阅读本文],我们通过示例和log来分析了Android的事件分发机制.这些,我们只是看到了现象,如果要进一步了解事件分发机制,这是不够的,我们还需要透过现象看本质,去研究研究源码.本文将从源码(基

Android自定义View(CustomCalendar-定制日历控件)

转载请标明出处: http://blog.csdn.net/xmxkf/article/details/54020386 本文出自:[openXu的博客] 目录: 1分析 2自定义属性 3onMeasure 4onDraw 绘制月份 绘制星期 绘制日期及任务 5事件处理 源码下载 ??应项目需求,需要做一个日历控件,效果图如下: ???? ??接到需求后,没有立即查找是否有相关开源日历控件可用.系统日历控件是否能满足 ,第一反应就是这个控件该怎么画?谁叫咱自定义控件技术牛逼呢O(∩_∩)O哈哈~

Android自定义View(三、深入解析控件测量onMeasure)

转载请标明出处: http://blog.csdn.net/xmxkf/article/details/51490283 本文出自:[openXu的博客] 目录: onMeasure什么时候会被调用 onMeasure方法执行流程 MeasureSpec类 从ViewGroup的onMeasure到View的onMeasure ViewGroup中三个测量子控件的方法 getChildMeasureSpec方法 View的onMeasure setMeasuredDimension ??在上一篇

【朝花夕拾】Android自定义View篇之(五)Android事件分发及传递机制

前言 在自定义View中,经常需要处理Android事件分发的问题,尤其在有多个输入设备(如遥控.鼠标.游戏手柄等)时,事件处理问题尤为突出.Android事件分发机制,一直以来都是一个让众多开发者困扰的难点,至少笔者在工作的前几年中,没有特意研究它之前,就经常云里雾里.实际上,该问题的“七寸”就是dispatchTouchEvent(MotionEvent ev).onInterceptTouchEvent(MotionEvent ev).onTouchEvent(MotionEvent ev

Android自定义View 简单实现多图片选择控件

前言 相信很多朋友在开发中都会遇到图片上传的情况,尤其是多图上传,最 经典的莫过于微信的图片选择了.所有很多情况下会使用到多图选择. 所以就有了这篇文章,今天抽点时间写了个控件. 支持自定义选择图片的样式 支持设置图片选择数量 支持图片预览,删除 支持图片拍照 先来看看效果 实现分析 假如不定义控件,我们要实现这样一个功能,无非是写个GridView在item点击的时候去显示图片进行选择,在返回界面的时候进行GridView的数据刷新.我们把这些逻辑写在我们自定义的GridView中,就成了一个

Android自定义View(LineBreakLayout-自动换行的标签容器)

??最近一段时间比较忙,都没有时间更新博客,今天公司的事情忙完得空,继续为我的自定义控件系列博客添砖加瓦.本篇博客讲解的是标签自动换行的布局容器,正好前一阵子有个项目中需要,想了想没什么难度就自己弄了.而自定义控件系列文章中对于自定义ViewGroup上次只是讲解了一些基础和步骤 Android自定义ViewGroup(四.打造自己的布局容器),这次就着这个例子我们来完成一个能在项目中使用的自定义布局容器. 1. 初步分析 ??首先我们看一看要完成的效果图: ?????? ??上面红色标示出的就

Android自定义View(RollWeekView-炫酷的星期日期选择控件)

转载请标明出处: http://blog.csdn.net/xmxkf/article/details/53420889 本文出自:[openXu的博客] 目录: 1分析 2定义控件布局 3定义CustomWeekView 4重写onMeasure 5点击后执行动画 7重置预备控件 源码下载 ??最近收到一个自定义控件的需求,需要做一个日期选择控件,实现图如下: ???? ??一次展示一个星期的5天,中间放大的为当前选中的:如果点击了其中一个日期,比如星期五,那么整体向左滑动,并将星期五慢慢放大

多点触控

1.要了解多点触控,我们必须先了解一下View的生命周期,毕竟在Android用的到多点触控的地方,一般都是自定义控件.就像Fragment和Activity都有生命周期一样,View也有自己的生命周期.该生命周期并不直接和展示它的Fragment或者Activity相连,相反,它和显示它的窗口状态已经渲染循环相关. 当视图被添加到View层次结构中时,第一个被回调的函数是View.onAttachedToWindow(),这标志着它现在可以加载所需的资源了,构建自定义视图时,开发者应该重载该方