前言

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

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

本文的主要内容如下:

一、实现单指拖动图片

要实现单指拖动图片,大致思路就是监控手指的ACTION_MOVE事件。手指移动过程中,获取事件的坐标,让图片根据坐标的变化来进行移动。具体代码实现如下,先自定义一个View,在其中处理单指拖动逻辑。

 public class SingleTouchDragView extends View {
private static final String TAG = "songzheweiwang";
private Bitmap mBitmap;
private RectF mRectF;
private Matrix mMatrix;
private Paint mPaint;
private PointF mLstPointF;
private boolean mCanDrag = false; public SingleTouchDragView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init();
} private void init() {
mPaint = new Paint();
mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.dog);
mRectF = new RectF(0, 0, mBitmap.getWidth(), mBitmap.getHeight());
mMatrix = new Matrix();
mLstPointF = new PointF();
} @Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
//判断按下位置是否在图片区域内
if (mRectF.contains(event.getX(), event.getY())) {
mCanDrag = true;
mLstPointF.set(event.getX(), event.getY());
}
break;
case MotionEvent.ACTION_UP:
mCanDrag = false;
case MotionEvent.ACTION_MOVE:
if (mCanDrag) {
//移动图片
mMatrix.postTranslate(event.getX() - mLstPointF.x, event.getY() - mLstPointF.y);
//更新触摸位置
mLstPointF.set(event.getX(), event.getY());
// 更新图片区域
mRectF = new RectF(0, 0, mBitmap.getWidth(), mBitmap.getHeight());
mMatrix.mapRect(mRectF);
//刷新
invalidate();
}
break;
}
//注意这里需要返回true,因为当前自定义view继承自基类View,默认是无法消费触摸事件的
return true;
} @Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawBitmap(mBitmap, mMatrix, mPaint);
}
}

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

 <?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"> <com.example.demos.customviewdemo.SingleTouchDragView
android:layout_width="match_parent"
android:layout_height="match_parent" />
</RelativeLayout>

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

                

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

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

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

 public class MultiTouchDragView extends View {
private static final String TAG = "songzheweiwang";
private Bitmap mBitmap;
private RectF mRectF;
private Matrix mMatrix;
private Paint mPaint;
private PointF mLstPointF;
private boolean mCanDrag = false; public MultiTouchDragView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init();
} private void init() {
mPaint = new Paint();
mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.dog);
mRectF = new RectF(0, 0, mBitmap.getWidth(), mBitmap.getHeight());
mMatrix = new Matrix();
mLstPointF = new PointF();
} @Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_POINTER_DOWN:
//pointerId为0的手指(即我们定义的第一根手指)按下在指定区域内
if (event.getPointerId(event.getActionIndex()) == 0 && mRectF.contains(event.getX(), event.getY())) {
mCanDrag = true;
//getX()和getY()没有传入参数时,默认传入的0
mLstPointF.set(event.getX(), event.getY());
}
break;
case MotionEvent.ACTION_MOVE:
if (mCanDrag) {
int pointerIndex = event.findPointerIndex(0);//第一根手指的pointerId为0
//这里需要注意,多手指频繁按下和抬起时可能会出现pointerIndex为-1的情况,如不处理,后面会报错
if (pointerIndex == -1) {
break;
}
mMatrix.postTranslate(event.getX(pointerIndex) - mLstPointF.x,
event.getY(pointerIndex) - mLstPointF.y);
mLstPointF.set(event.getX(pointerIndex), event.getY(pointerIndex));
mRectF = new RectF(0, 0, mBitmap.getWidth(), mBitmap.getHeight());
mMatrix.mapRect(mRectF);
invalidate();
}
break;
case MotionEvent.ACTION_POINTER_UP:
case MotionEvent.ACTION_UP:
if (event.getPointerId(event.getActionIndex()) == 0) {
mCanDrag = false;
}
break;
}
return true;
} @Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawBitmap(mBitmap, mMatrix, mPaint);
}
}

由于我们要实现的效果是只有第一根手指可以拖动图片,所以在第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中以活动手指为基础来确定滑动操作。仍然在上述代码基础上修改来实现。

 public class MultiTouchDragView2 extends View {
private static final String TAG = "songzheweiwang";
private Bitmap mBitmap;
private RectF mRectF;
private Matrix mMatrix;
private Paint mPaint;
private PointF mLstPointF;
private boolean mCanDrag = false;
private int mActivePointerId;
private final int INVALID_POINTER = -1; public MultiTouchDragView2(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init();
} private void init() {
mPaint = new Paint();
mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.dog);
mRectF = new RectF(0, 0, mBitmap.getWidth(), mBitmap.getHeight());
mMatrix = new Matrix();
mLstPointF = new PointF();
} @Override
public boolean onTouchEvent(MotionEvent event) {
int actionIndex = event.getActionIndex();
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
//getX()和getY()没有传入参数时,默认传入的0
if (mRectF.contains(event.getX(), event.getY())) {
mActivePointerId = 0; //第一根手指按下时,pointerId和pointerIndex都为0
mCanDrag = true;
mLstPointF.set(event.getX(), event.getY());
}
break;
case MotionEvent.ACTION_POINTER_DOWN:
//有新落下的手指,则将新落下的手指作为活动手指,保存下活动手指的坐标
mActivePointerId = event.getPointerId(actionIndex);
mLstPointF.set(event.getX(actionIndex), event.getY(actionIndex));
break;
case MotionEvent.ACTION_MOVE:
if (mActivePointerId == INVALID_POINTER) {
break;
}
if (mCanDrag) {
//这里根据活动手指的pointerId来找到pointerIndex,而不再是固定的手指的pointerId了
int pointerIndex = event.findPointerIndex(mActivePointerId);
if (pointerIndex == -1) {
break;
}
mMatrix.postTranslate(event.getX(pointerIndex) - mLstPointF.x,
event.getY(pointerIndex) - mLstPointF.y);
mLstPointF.set(event.getX(pointerIndex), event.getY(pointerIndex));
mRectF = new RectF(0, 0, mBitmap.getWidth(), mBitmap.getHeight());
mMatrix.mapRect(mRectF);
invalidate();
}
break;
case MotionEvent.ACTION_POINTER_UP:
//如果当前抬起的手指为活动手指,那么活动手指就传给留下的手指中pointerIndex最前面的一个
if (mActivePointerId == event.getPointerId(actionIndex)) {
int newPointerIndex = actionIndex == 0 ? 1 : 0;
mActivePointerId = event.getPointerId(newPointerIndex);
mLstPointF.set(event.getX(newPointerIndex), event.getY(newPointerIndex));
}
break;
case MotionEvent.ACTION_UP:
//最后一根手指也抬起来了
mActivePointerId = INVALID_POINTER;
mCanDrag = false;
break;
}
return true;
} @Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawBitmap(mBitmap, mMatrix, mPaint);
}
}

由于需要依据活动的手指来拖动图片,所以需要实时记录下活动手指的坐标,如第40、54、65行所示。依然用两根手指依次拖动图片,效果如下所示:

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

结语

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

参考文章

Android多点触控最佳实践

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

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

  1. 【朝花夕拾】Android自定义View篇之(八)多点触控(上)MotionEvent简介

    前言 在前面的文章中,介绍了不少触摸相关的知识,但都是基于单点触控的,即一次只用一根手指.但是在实际使用App中,常常是多根手指同时操作,这就需要用到多点触控相关的知识了.多点触控是在Android2 ...

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

    前言 转载请注明,转自[https://www.cnblogs.com/andy-songwei/p/11039252.html]谢谢! 在上一篇文章[[朝花夕拾]Android自定义View篇之(五 ...

  3. 【朝花夕拾】Android自定义View篇之(四)自定义View的三种实现方式及自定义属性使用介绍

    前言 转载请声明,转自[https://www.cnblogs.com/andy-songwei/p/10979161.html],谢谢! 尽管Android系统提供了不少控件,但是有很多酷炫效果仍然 ...

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

    转载请标明出处: http://blog.csdn.net/xmxkf/article/details/54020386 本文出自:[openXu的博客] 目录: 1分析 2自定义属性 3onMeas ...

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

    转载请标明出处: http://blog.csdn.net/xmxkf/article/details/51490283 本文出自:[openXu的博客] 目录: onMeasure什么时候会被调用 ...

  6. (一)自定义ImageView,初步实现多点触控、自由缩放

    真心佩服那些一直专注于技术共享的大神们,正是因为他们无私的分享精神,我才能每天都有进步.近日又算是仔细学了android的自定义控件技术,跟着大神的脚步实现了一个自定义的ImageView.里面涉及到 ...

  7. 【朝花夕拾】Android自定义View篇之(一)View绘制流程

    前言 转载请申明转自[https://www.cnblogs.com/andy-songwei/p/10955062.html]谢谢! 自定义View.多线程.网络,被认为是Android开发者必须牢 ...

  8. 【朝花夕拾】Android自定义View篇之(五)Android事件分发机制(上)Touch三个重要方法的处理逻辑

    前言 转载请注明,转自[https://www.cnblogs.com/andy-songwei/p/10998855.html]谢谢! 在自定义View中,经常需要处理Android事件分发的问题, ...

  9. 【朝花夕拾】Android自定义View篇之(十一)View的滑动,弹性滑动与自定义PagerView

    前言 由于手机屏幕尺寸有限,但是又经常需要在屏幕中显示大量的内容,这就使得必须有部分内容显示,部分内容隐藏.这就需要用一个Android中很重要的概念——滑动.滑动,顾名思义就是view从一个地方移动 ...

随机推荐

  1. Python缺乏调查的陷阱 动态实例属性、引用、逃生

    --看到哪里.想到哪里,记到哪里 非常多时候.非常多人学python的时候,会忽略的东西非常多.大多数都盯着能"出货"即可,可是通常在读别人的代码的时候发现,看不懂...一方面是自 ...

  2. CSharp获取图形文件的读写

    C#是微软发布了一个面向对象.开展对.NET Framework上述高级编程语言.并定于占领在微软开发者论坛(PDC)在首演. C#这是微软研究员Anders Hejlsberg最新成就.C#容貌Ja ...

  3. uboot通过使用U磁盘引导内核RT5350成功

    今天,在下次尝试使用16G 的u菜.这让两个分区,A位于zimage.一家商店rootfs:在uboot加载分区zimage并成功推出! RT5350 # fatload usb 0:1 0x80c0 ...

  4. 注册.NET Framework 到IIS服务器

    一.注册.NET 4.0 32位的Windows:--------------------------------------------------------------------------- ...

  5. C# WebClient的使用

    using System;using System.Collections.Generic;using System.ComponentModel;using System.Data;using Sy ...

  6. 解决Android Studio运行时报Error:java.lang.NullPointerException (no error message)错误

    原文:解决Android Studio运行时报Error:java.lang.NullPointerException (no error message)错误                    ...

  7. 图像滤镜艺术---Oilpaint油画滤镜

    原文:图像滤镜艺术---Oilpaint油画滤镜  Oilpaint油画滤镜     图像油画效果实际上是将图像边缘产生一种朦胧,雾化的效果,同时,将一定的边缘模糊化,这样图像整体上看去像素与像素之间 ...

  8. UWP入门(六)-- ResourceDictionary 和 XAML 资源引用

    原文:UWP入门(六)-- ResourceDictionary 和 XAML 资源引用 你最希望声明为 XAML 资源的 XAML 元素包括 Style.ControlTemplate.动画组件和 ...

  9. The specified type member 'IsLock' is not supported in LINQ to Entities. Only initializers, entity members, and entity navigation properties are supported.

    var query = from C in objDb.GetDb<A>() join a in objDb.GetDb<B>().Where(m => m.Comput ...

  10. Elasticsearch教程(一)简介与安装

    简单概念 ElasticSearch是一个基于Lucene的搜索服务器.它提供了一个分布式多用户能力的全文搜索引擎,基于RESTful web接口.Elasticsearch是用Java开发的,并作为 ...