文章目录
文章索引
- Android 视频手势缩放与回弹动效实现(一):主要是实现视频双指:缩放、平移、回弹动效
- Android 视频旋转、缩放与回弹动效实现(二):主要是实现视频双指:旋转、缩放、平移、回弹动效
Android 视频旋转、缩放与回弹动效实现(二)
在Android 视频手势缩放与回弹动效实现(一)中我们实现了下列1-5部分的需求,事实上对于双指手势触摸,不仅可以缩放、平移,还可以进行旋转,现在我们就在原有基础上进行改造,添加视频手势旋转及回弹动效,来实现需求6。
功能需求
- 双指缩放视频播放画面,支持设定最小、最大缩放范围
- 双指拖动画面可任意方向移动
- 如果是缩小画面,最后需要在屏幕居中显示,并且需要有动画效果
- 如果是放大画面,有画面边缘在屏幕内的,需要自动吸附到屏幕边缘
- 视频暂停状态下也能缩放
- 双指旋转画面,旋转角度以45度为分界线,进行自动校正(超过45度旋转90度;不超过45度旋转0度)
实现思路
实现主要思路
- 手势旋转识别。
接收onTouchEvent事件,识别双指旋转:旋转角度、旋转中心 - 手势旋转处理。
识别到手势旋转后,通过Matrix.postRotate
进行画面旋转变换 - 缩放倍数逻辑改造。
由于旋转也会引起Matrix#MSCALE_X
值发生变化,缩放倍数不能再通过直接获取矩阵该分量值来计算,需要将计算后的缩放倍数独立保存。参考:Android Matrix 带你掌控雷电,旋转部分对Matrix
的影响 - 回弹动效触发。
原有回弹动效是在缩放结束后onScaleEnd
触发,加入旋转后,需要保证在缩放结束onScaleEnd
和旋转结束onRotateEnd
后才触发,这里选择ACTION_UP
来触发回弹动效。 - 回弹动效的计算。
加入旋转后,回弹动效在原有2个数据:transAnimX
、transAnimY
(x、y轴上的平移补偿)基础上增加了旋转补偿角度rotateEndFixDegrees
。
1. 旋转识别
在缩放、平移中我们使用系统自带缩放识别类ScaleGestureDetector.onTouchEvent(event)
来识别当前触摸事件,得到onScaleBegin
、onScale
、onScaleEnd
的缩放回调,然后用VideoTouchScaleHandler
来对缩放、平移进行相应处理。
加入旋转功能后,由于系统没有自带旋转识别类,我们需要自定义实现手势识别旋转:RotateGestureDetector
。下面介绍RotateGestureDetector
里面的主要逻辑,即如何识别旋转中心与旋转角度。
-
旋转中心
旋转中心计算比较简单,通过获取双指间连线的中心点即可。
但是实际我们在视频画面旋转中,不会使用双指中心来进行旋转,而是直接使用画面中心来旋转,为什么呢?如果采用双指中心来旋转,因为每次旋转的中心点都不固定,会造成旋转时,变换矩阵中的位移分量也受到了影响,这样后续在计算回弹动效的时候,需要补偿的位移
transAnimX
、transAnimY
会因为旋转中心点的变化,而无法准确计算出。// 计算双指旋转中心点 float pivotX = (event.getX(0) + event.getX(1)) / 2; float pivotY = (event.getY(0) + event.getY(1)) / 2; // 实际播放画面的旋转中心为画面中心点 mRotateCenter = new PointF(mTouchAdapter.getTextureView().getWidth() / 2, mTouchAdapter.getTextureView().getHeight() / 2);
-
旋转角度
旋转角度的计算稍微比较复杂,我们需要先计算当前双指连线的夹角degrees, 来减去上一次我们记录的手指连线夹角lastDegrees,才能得到本次旋转的角度diffDegree = degrees - lastDegrees。如图所示:
双指连线夹角degrees
如何计算呢?
这里我们需要获取到双指分别在x、y轴上的分量 Δ x , Δ y \Delta x, \Delta y Δx,Δy,组成的三角形,由于 t a n ( θ ) = Δ y Δ x tan(\theta)=\frac{\Delta y}{\Delta x} tan(θ)=ΔxΔy通过计算夹角的反正切三角函数arctangent得到偏移角度 θ = a r c t a n Δ y Δ x \theta=arctan \frac{\Delta y}{\Delta x} θ=arctanΔxΔy。几个注意的小点:
-
Math.atan2(y, x)
和Math.atan(v)
差异。都能计算反正切,为了避免除数为0的情况(当然还有其它差别,如结果范围不同),我们一般选用
Math.atan2(y, x)
-
Math.atan2(y, x)
结果。计算结果值为弧度,取值范围**[-Math.PI, Math.PI],由于旋转操作
Matrix.postRotate
传入的参数为具体角度,所以我们需要将弧度通过Math.toDegrees
转换为角度** [-180, 180]。 -
分母微小抖动带来的角度剧烈变化。
试想一下 Δ y Δ x \frac{\Delta y}{\Delta x} ΔxΔy,当 Δ y \Delta y Δy一定情况下, Δ x \Delta x Δx轴偏移量稍微变化点点,都会引起角度剧烈变化,显然不符合用户的真实意图,所以对于超过45度的变化,我们可以认为只改变少许角度,这里取值正负5度。
-
旋转角度
diffDegree
>0,则通过Matrix.postRotate
传入角度进行旋转,结果为正数是顺时针旋转,否则是逆时针旋转
// 计算双指旋转中心,使手势识别工具类具备识别旋转中心能力,但本次需求实际并不会使用,而是始终用画面中心来旋转
float pivotX = (event.getX(0) + event.getX(1)) / 2;
float pivotY = (event.getY(0) + event.getY(1)) / 2;
float deltaX = event.getX(0) - event.getX(1);
float deltaY = event.getY(0) - event.getY(1);
float degrees = (float) Math.toDegrees(Math.atan2(deltaY, deltaX));
// 计算本次旋转角度
float diffDegree = degrees - mLastDegrees; // 旋转角度 = 当前双指夹角 - 上次双指夹角。结果大于0 顺时针旋转;小于0逆时针旋转
if (diffDegree > 45) {
// y/x 分母微小抖动带来的角度剧烈变化,修正
diffDegree = -5;
} else if (diffDegree < -45) {
diffDegree = 5;
}
下面是旋转识别类RotateGestureDetector
的所有源码
旋转识别:RotateGestureDetector
/**
* 手势旋转识别
* <p>
*
* @author yinxuming
* @date 2020/12/22
*/
public class RotateGestureDetector {
private OnRotateGestureListener mRotateGestureListener;
private boolean mIsRotate = false;
private float lastDegrees;
public boolean onTouchEvent(MotionEvent event) {
if (event.getPointerCount() != 2) {
mIsRotate = false;
return false;
}
float pivotX = (event.getX(0) + event.getX