前言

在上次的文章:图片操作系列 —(1)手势缩放图片功能中,我们已经学会了如何用手势来对图片进行缩放。这次我们继续来看第二个操作,那就是如何用手势来旋转图片。

所以我们本文我们一共要实现二个功能:

  1. 根据二个手指头的旋转来使图片跟着旋转
  2. 当二个手指头放开后,图片会自动回归到合适的位置。

我说明下第二个功能点的意思:什么叫回归到合适的位置,比如如图一,我们只转动了一点点,没有超过45度,然后放在手指,然后就会回到图二的样子。但是如果超过了45度,然后放开手指,就回变成图三的样子。

图一
图二
图三

前面基本的东西说明我都不说了。比如Matrix等知识。大家可以直接参考图片操作系列 —(1)手势缩放图片功能。

ps:我这边可以再贴出相关基础的链接:
android matrix 最全方法详解与进阶(完整篇)
Android Matrix


根据二个手指头的旋转来使图片跟着旋转:

我们知道使图片进行旋转特定的角度很简单:

使用Matrix.postRotate(float degrees, float px, float py)方法即可。绕着(px,py)点进行旋转degrees角度。

所以我们的问题就变成了如果获取二个手指头在做旋转手势的时候,相应的角度的变化,从而通过Matrix.postRotate方法来让图片也跟着变化。

1.获取二个手指头的手势监听

在图片操作系列 —(1)手势缩放图片功能文中我们知道,控制图片的缩放是专门有个ScaleGestureDetector;在OnTouch事件中把相应的事件传递给ScaleGestureDetector。然后监听处理。我们也可以模仿着写一个RotateGestureDetector来进行图片旋转的监听和处理。

public interface IRotateDetector {/*** handle rotation in onTouchEvent** @param event The motion event.* @return True if the event was handled, false otherwise.*/boolean onTouchEvent(MotionEvent event);/*** is the Gesture Rotate** @return true:rotating;false,otherwise*/boolean isRotating();
}复制代码
public class RotateGestureDetector implements IRotateDetector{private int mLastAngle = 0;//最后一次的角度值private IRotateListener mListener;//用来旋转的回调Listenerprivate boolean mIsRotate;//是否处于旋转//用来设置回调Listener的方法public void setRotateListener(IRotateListener listener) {this.mListener = listener;}//用来接收触摸事件@Overridepublic boolean onTouchEvent(MotionEvent event) {return doRotate(event);}//真正的计算手势操作所得到的角度值的方法,及回调调用。private boolean doRotate(MotionEvent ev) {if (ev.getPointerCount() != 2) {return false;}//Calculate the angle between the two fingersint pivotX = (int) (ev.getX(0) + ev.getX(1)) / 2;int pivotY = (int) (ev.getY(0) + ev.getY(1)) / 2;float deltaX = ev.getX(0) - ev.getX(1);float deltaY = ev.getY(0) - ev.getY(1);double radians = Math.atan(deltaY / deltaX);int degrees = (int) Math.round(Math.toDegrees(Math.atan2(deltaY,deltaX)));switch (ev.getActionMasked()) {case MotionEvent.ACTION_DOWN:mLastAngle = degrees;mIsRotate = false;break;case MotionEvent.ACTION_UP:mIsRotate = false;break;case MotionEvent.ACTION_POINTER_DOWN:mLastAngle = degrees;mIsRotate = false;break;case MotionEvent.ACTION_CANCEL:case MotionEvent.ACTION_POINTER_UP:mIsRotate = false;upRotate(pivotX, pivotY);mLastAngle = degrees;break;case MotionEvent.ACTION_MOVE:mIsRotate = true;int degreesValue = degrees  - mLastAngle;if (degreesValue > 45) {//Going CCW across the boundaryrotate(-5, pivotX, pivotY);} else if (degreesValue < -45) {//Going CW across the boundaryrotate(5, pivotX, pivotY);} else {//Normal rotation, rotate the differencerotate(degreesValue, pivotX, pivotY);}//Save the current anglemLastAngle = degrees;break;}return true;}//回调的方法之一:控制图片根据手势的变化实时进行旋转private void rotate(int degree, int pivotX, int pivotY) {if (mListener != null) {mListener.rotate(degree, pivotX, pivotY);}}//回调的方法之一:最后某个手指放开后,控制图片自动回归到合适的位置。private void upRotate(int pivotX, int pivotY) {if (mListener != null) {mListener.upRotate(pivotX, pivotY);}}}复制代码

2.获取二个手指头的角度变化

所以我们只需要来分析一下具体OnTouch事件中的doRotate方法即可:

//真正的计算手势操作所得到的角度值的方法,及回调调用。
private boolean doRotate(MotionEvent ev) {//如果触摸的手指头不是2个,直接返回。if (ev.getPointerCount() != 2) {return false;}//获取二个手指头的中心点的X与Y值,等会选择二个手指头的中心点作为旋转的中心int pivotX = (int) (ev.getX(0) + ev.getX(1)) / 2;int pivotY = (int) (ev.getY(0) + ev.getY(1)) / 2;//获取二个手指头之间的X和Y的差值float deltaX = ev.getX(0) - ev.getX(1);float deltaY = ev.getY(0) - ev.getY(1);//获取角度int degrees = (int) Math.round(Math.toDegrees(Math.atan2(deltaY,deltaX)));switch (ev.getActionMasked()) {case MotionEvent.ACTION_DOWN:mLastAngle = degrees;mIsRotate = false;break;case MotionEvent.ACTION_UP:mIsRotate = false;break;case MotionEvent.ACTION_POINTER_DOWN:mLastAngle = degrees;mIsRotate = false;break;case MotionEvent.ACTION_CANCEL:case MotionEvent.ACTION_POINTER_UP:mIsRotate = false;upRotate(pivotX, pivotY);mLastAngle = degrees;break;case MotionEvent.ACTION_MOVE:mIsRotate = true;/*每次把上一次的角度赋值给mLastAngle,然后获取当前新获取的角度degrees,二者相减获取到二个手指头在移动的时候相应的角度变化。*/int degreesValue = degrees  - mLastAngle;/*这里主要出现这么个情况,二个手指头如果相隔有一段距离,那么在移动的过程中,角度不会一下子变化很大.但是比如我们这里故意二个手指头是碰在一起的,然后二个手指头稍微动一下,你就会发现角度变化会很大。这样图片就会瞬间也旋转了很大的角度,让人体验感觉很怪,所以我们这里瞬间顺时针或者逆时针超过45度,都只移动5度值。*/if (degreesValue > 45) {rotate(-5, pivotX, pivotY);} else if (degreesValue < -45) {rotate(5, pivotX, pivotY);} else {rotate(degreesValue, pivotX, pivotY);}//Save the current anglemLastAngle = degrees;break;}return true;
}复制代码

doRotate方法中最主要的就是根据二个手指头触摸获取到的X,Y的差值,根据Math.atan2来获取到角度。我们具体来看下为什么这样可以来获取角度:

先附上一个基础概念:Math.atan与Math.atan2

假设我们先点击了(50,50),再点击(10,10),这时候我们的deltaX = 40,deltaY = 40;也就是说
我们的弧度就是Math.atan2(40,40),而角度就是再用Math.toDegrees对弧度进行转换即可。最终获得额角度是45度。

我们可以通过图形来查看为什么Math.atan2(40,40)对应的角度是45度。

如果我们的第二个手指头从(10,10)移动到了(50,10),也就是说最后变成了Math.atan2(40,0),根据图形来看我们就知道是:

所以一共旋转了45度,所以我们的图片也跟着顺时针旋转45度即可。

那假如我们的二个手指头的放入顺序反过来,变成:

那这时候就变成了Math.atan2(-40,-40),我们根据图形就知道了角度:

这时候还是跟刚才一样的操作,把(10,10)这个点移动到了(50,10),那这时候就是Math.atan2(-40,0);

所以最终得到的旋转的角度是(-135)-(-90) = 45度,所以最终也是顺时针旋转45度。所以我们不管是哪个手指头先放下都不影响结果。

也许有人就会问了,你这边按照二个手指的中点作为旋转中心去旋转,岂不是会旋转超出原来的图片的边界。如果你还记得我们上一篇文章:图片操作系列 —(1)手势缩放图片功能,这篇文章最后的内容讲的就是当图片超过边界,如果能随着手势慢慢回到边界里面:checkMatrixBounds()

3.在Activity中设置Listener来进行图片的旋转

然后我们只需要在相应的Activity处对回调回来的(degreesValue, pivotX, pivotY)三个值做相应的旋转即可。

rotateGestureDetector.setRotateListener(new IRotateListener() {@Overridepublic void rotate(int degree, int pivotX, int pivotY) {//图片跟着手势进行旋转mSuppMatrix.postRotate(degree, pivotX, pivotY);//Post the rotation to the imagecheckAndDisplayMatrix();}@Overridepublic void upRotate(int pivotX, int pivotY) {//当手指头松开的时候,让图片自动更新到合适的位置。float[] v = new float[9];mSuppMatrix.getValues(v);// calculate the degree of rotationint angle = (int) Math.round(Math.toDegrees(Math.atan2(v[Matrix.MSKEW_Y], v[Matrix.MSCALE_X])));mRightAngleRunnable = new RightAngleRunnable(angle, pivotX, pivotY);photoView.post(mRightAngleRunnable);}
});复制代码

手指头松开手图片自动旋转到合适位置:

我们知道,前面图片跟着旋转,是获取到了(int degree, int pivotX, int pivotY)这三个值,然后让mSuppMatrix.postRotate(degree, pivotX, pivotY);那我们就当手指头松开的时候,获取到最终这个图片比原来变化了多少角度即可。然后根据这个当前最终图片的变化角度来进行适当的旋转,让其旋转到合适位置。

我们来具体看怎么实现的:

@Override
public void upRotate(int pivotX, int pivotY) {//当手指头松开的时候,让图片自动更新到合适的位置。float[] v = new float[9];mSuppMatrix.getValues(v);// calculate the degree of rotationint angle = (int) Math.round(Math.toDegrees(Math.atan2(v[Matrix.MSKEW_Y], v[Matrix.MSCALE_X])));mRightAngleRunnable = new RightAngleRunnable(angle, pivotX, pivotY);photoView.post(mRightAngleRunnable);
}复制代码

Matrix,中文里叫矩阵,高等数学里有介绍,在图像处理方面,主要是用于平面的缩放、平移、旋转等操作。在Android里面,Matrix由9个float值构成,是一个3*3的矩阵。最好记住。如下图:

我们发现mSuppMatrix.getValues(v)方法返回的9个float值中,第一个为cosX,第四个为sinX,所以我们就取下标为0和3的值,也就是MSCALE_X和MSKEW_Y。我们用Math.atan2(v[Matrix.MSKEW_Y], v[Matrix.MSCALE_X])来获取弧度。再用Math.toDegrees来获取相应的最终图片的旋转的度数。

public class Matrix {public static final int MSCALE_X = 0;   //!< use with getValues/setValuespublic static final int MSKEW_X  = 1;   //!< use with getValues/setValuespublic static final int MTRANS_X = 2;   //!< use with getValues/setValuespublic static final int MSKEW_Y  = 3;   //!< use with getValues/setValuespublic static final int MSCALE_Y = 4;   //!< use with getValues/setValuespublic static final int MTRANS_Y = 5;   //!< use with getValues/setValuespublic static final int MPERSP_0 = 6;   //!< use with getValues/setValuespublic static final int MPERSP_1 = 7;   //!< use with getValues/setValuespublic static final int MPERSP_2 = 8;   //!< use with getValues/setValues..................
}复制代码

然后我们再把获取到的角度和中心点,通过一个Runnable来进行图片最后的矫正:

mRightAngleRunnable = new RightAngleRunnable(angle, pivotX, pivotY);
photoView.post(mRightAngleRunnable);复制代码

我们知道最后是RightAngleRunnable来进行图片的矫正,所以我们具体来分析下这个Runnable:

class RightAngleRunnable implements Runnable {private static final int RECOVER_SPEED = 4;private int mOldDegree;private int mNeedToRotate;private int mRoPivotX;private int mRoPivotY;RightAngleRunnable(int degree, int pivotX, int pivotY) {Log.v("dyp4", "oldDegree:" + degree + "," + "calDegree:" + calDegree(degree));this.mOldDegree = degree;this.mNeedToRotate = calDegree(degree);this.mRoPivotX = pivotX;this.mRoPivotY = pivotY;}//最终计算需要矫正的角度值/*例如:比如最终是60度,这时候其实是超过了45度,应该矫正成90度,所以最终要多给它30度。顺时针多选择30度。这里计算会得到30。比如如果是-60度,这时候应该是变成-90读,所以我们逆时针多旋转30度。这时候计算会得到-30。如果是20度,这时候没有超过45度,所以应该矫正成0度,所以最终要逆时针转回20度,所以这里计算会得到-20。如果是-120度,这时候要变成-90度,所以要顺时针转回30度,所以计算会得到30。*/private int calDegree(int oldDegree) {int N = Math.abs(oldDegree) / 45;if ((0 <= N && N < 1) || 2 <= N && N < 3) {return -oldDegree % 45;} else {if (oldDegree < 0) {return -(45 + oldDegree % 45);} else {return (45 - oldDegree % 45);}}}/*我们上面的calDegree方法可以获得我们需要矫正的角度,但是我们不是一下子就让图片选择N度,而是慢慢的转过来。比如我们用RECOVER_SPEED = 4,4度的慢慢来旋转过来,不会给用户很突兀的感觉。*/@Overridepublic void run() {if (mNeedToRotate == 0) {return;}if (photoView == null) {return;}if (mNeedToRotate > 0) {//Clockwise rotationif (mNeedToRotate >= RECOVER_SPEED) {mSuppMatrix.postRotate(RECOVER_SPEED, mRoPivotX, mRoPivotY);mNeedToRotate -= RECOVER_SPEED;} else {mSuppMatrix.postRotate(mNeedToRotate, mRoPivotX, mRoPivotY);mNeedToRotate = 0;}} else if (mNeedToRotate < 0) {//Counterclockwise rotationif (mNeedToRotate <= -RECOVER_SPEED) {mSuppMatrix.postRotate(-RECOVER_SPEED, mRoPivotX, mRoPivotY);mNeedToRotate += RECOVER_SPEED;} else {mSuppMatrix.postRotate(mNeedToRotate, mRoPivotX, mRoPivotY);mNeedToRotate = 0;}}checkAndDisplayMatrix();Compat.postOnAnimation(photoView, this);}}复制代码

结尾

还是老样子,希望大家不要吐槽。有问题留言哈哈。。O(∩_∩)O哈哈~
PS:有好的画图软件介绍吗。。求介绍o(╥﹏╥)o

附上Demo地址:ScaleImageVewDemo(已经把图片旋转的Activity demo 加入里面)

图片操作系列 —(2)手势旋转图片相关推荐

  1. 疯狂ios讲义之实例:通过旋转手势旋转图片

    实例:通过旋转手势旋转图片 本实例将会对前面的实例进行改进,在前面实例的基础上增加一个旋转手势处理器,从而让该应用既可根据用户捏合手势对图片进行缩放,也可根据用户旋转手势对图片进行旋转. 复制上面的应 ...

  2. Android之GestureDetector-简单手势操作及通过手势缩放图片,我离职后面试收割小米等大厂offer

    Android提供了手势检测,并为其提供了相应的监听器,需要用到的类是GestureDetector,其实例代表了一个手势检测器,创建GestureDetector时需要传入一个GestureDete ...

  3. Android之GestureDetector-简单手势操作及通过手势缩放图片

     Android提供了手势检测,并为其提供了相应的监听器,需要用到的类是GestureDetector,其实例代表了一个手势检测器,创建GestureDetector时需要传入一个GestureDet ...

  4. css和js实现3d图片,JavaScript_纯JS实现旋转图片3D展示效果,CSS:style type=text/cssgt - phpStudy...

    纯JS实现旋转图片3D展示效果 CSS: #show{position:relative;margin:20px auto;width:800px;} .item{position:absolute; ...

  5. php 图片 旋转图片吗,php能实现旋转图片吗

    php能旋转图片,其实现方法是:首先创建图像资源:然后使用"imagerotate()"函数按指定的角度旋转:最后通过"$imagejpeg($rotate,$filen ...

  6. android progressbar 自定义圆形旋转图片,Android progressBar 自定义圆形旋转图片

    项目需要中需要更换progressbar的旋转背景,在网上找了几种办法,但是都有各自的问题 于是结合网上所讲,研究了一下终于ok了: 一 首相在drawable文件夹中建立如下旋转动画文件 < ...

  7. canva旋转图片 js_js和canvas实现旋转图片

    //点击向左向右旋转图片 window.οnlοad=function(){ var Img=document.getElementById('img'); var inPut=document.ge ...

  8. bootstrap-wysiwyg 结合 base64 解码 .net bbs 图片操作类 (三) 图片裁剪

    官方的例子 是 长方形的. 我这里 用于 正方形的头像 所以  做如下  修改 #preview-pane .preview-container {width: 73px;height: 73px;o ...

  9. python 生成pdf 文字和图片_Python系列—PDF文本与图片抽取

    PDF是人们日常使用最多的跨平台文档.其是一种用独立于应用程序.硬件.操作系统的方式呈现文档的文件格式.每个PDF文件包含固定布局的平面文档的完整描述,包括文本.字形.图形及其他需要显示的信息.具有良 ...

最新文章

  1. leetcode dfs_深度优先搜索:具有6个Leetcode示例的DFS图遍历指南
  2. 深度解析iPhone Category用法
  3. 注解RequestMapping中的URI路径最前面到底需不需要加斜线?
  4. DELPHI的DBGRID有两个难点
  5. c3p0获取连接Connection后的Close()---释疑
  6. slf4j 和 log4j的关系及合用Maven配置
  7. linux内核通用提权漏洞expliot 脏牛Dirty COW
  8. 查看mysql数据库版本_MySQL数据库之查看MySQL版本的四种方法
  9. 北京大学Cousera学习笔记--3-计算导论与C语言基础-第一讲.计算机的基本原理-计算机怎么计算-数的二进制...
  10. 【转】WebApi 身份认证解决方案:Basic基础认证
  11. Springboot2 Quartz实现JAVA定时任务的动态配置
  12. rabbitmq之SimpleMessageListenerContainer监听容器
  13. 中国各省份边界json文件
  14. 医院病历html模板,三甲医院电子病历模板参考
  15. 有功,无功,视在功率的关系公式和图
  16. elang 游戏 生成全局id
  17. HTML5轻松实现全屏视频背景
  18. git reset命令详解
  19. Hibernate QBC与QBE
  20. OSPF中Stub、完全Stub、NSSA、完全NSSA区域

热门文章

  1. python调用c/c++代码以及解决ctypes.ArgumentError: argument 1: class 'TypeError': Don't know how to convert
  2. oracle wallet java_Java JDBC和Oracle Wallet Connection
  3. 设计模式 | 中介者模式及典型应用
  4. The font Noto Sans CJK SC DemiLight cannot be found
  5. Android驱动面阵相机,支持像移补偿功能面阵CCD相机驱动电路系统
  6. 基于letNet模型的minst手写体识别
  7. 想要做好软件测试,可以先了解AST、SCA和渗透测试
  8. ASW3410 10GHz/USB3.1高速模拟切换芯片规格分享完美代替ONSEMI安森美FUSB340
  9. android沉浸式导航栏与键盘的冲突
  10. 用Python在Excel中查找并替换数据