Android 视频旋转、缩放与回弹动效实现(二)
文章目录
- Android 视频旋转、缩放与回弹动效实现(二)
- 功能需求
- 实现思路
- 1. 旋转识别
- 旋转识别:RotateGestureDetector
- 2. 旋转处理
- 旋转处理:VideoTouchRotateHandler
- 3. 回弹动效
- 1. 动效触发时机
- 双指触摸onTouchEvent回调顺序
- 2. 动效参数计算
- 3. 连续回弹动效处理
- 回弹动效参数计算:VideoTouchFixEndAnim
- 回弹动效执行:ScaleRotateEndAnimator
- 项目完整代码
- 参考
文章索引
- 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(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)); // 当前双指连线夹角switch (event.getActionMasked()) {case MotionEvent.ACTION_DOWN:case MotionEvent.ACTION_POINTER_DOWN:lastDegrees = degrees;mIsRotate = false;break;case MotionEvent.ACTION_MOVE:if (!mIsRotate) {mIsRotate = true;notifyRotateBegin();}float diffDegree = degrees - lastDegrees; // 旋转角度 = 当前双指夹角 - 上次双指夹角。结果大于0 顺时针旋转;小于0逆时针旋转if (diffDegree > 45) { // y/x 分母微小抖动带来的角度剧烈变化,修正diffDegree = -5;} else if (diffDegree < -45) {diffDegree = 5;}notifyRotate(diffDegree, pivotX, pivotY);lastDegrees = degrees;break;case MotionEvent.ACTION_POINTER_UP:case MotionEvent.ACTION_CANCEL:lastDegrees = 0;mIsRotate = false;notifyRotateEnd();break;}return true;}private void notifyRotateBegin() {if (mRotateGestureListener != null) {mRotateGestureListener.onRotateBegin(this);}}private void notifyRotate(float diffDegree, float pivotX, float pivotY) {if (mRotateGestureListener != null) {mRotateGestureListener.onRotate(this, diffDegree, pivotX, pivotY);}}private void notifyRotateEnd() {if (mRotateGestureListener != null) {mRotateGestureListener.onRotateEnd(this);}}public void setRotateGestureListener(OnRotateGestureListener rotateGestureListener) {mRotateGestureListener = rotateGestureListener;}public interface OnRotateGestureListener {boolean onRotateBegin(RotateGestureDetector detector);boolean onRotate(RotateGestureDetector detector, float degrees, float px, float py);void onRotateEnd(RotateGestureDetector detector);}public static class SimpleOnRotateGestureListener implements OnRotateGestureListener {@Overridepublic boolean onRotateBegin(RotateGestureDetector detector) {return false;}@Overridepublic boolean onRotate(RotateGestureDetector detector, float degrees, float px, float py) {return false;}@Overridepublic void onRotateEnd(RotateGestureDetector detector) {}}
}
2. 旋转处理
旋转处理,我们通过VideoTouchRotateHandler
来进行旋转处理。主要功能是实现旋转识别RotateGestureDetector.OnRotateGestureListener
的接口,然后注册到旋转识别工具中RotateGestureDetector
,来接收旋转识别结果。
最后使用Matrix.postRotate(degrees, px, py)
来对画面进行旋转。几个注意点:
Matrix.postRotate(degrees, px, py)
与Matrix.postRotate(degrees)
区别。
不带旋转中心参数的方法,默认旋转中心是控件本身左上角,所以我们必须使用带旋转中心参数的方法,设置旋转中心为TextureView
中心点位置。累计旋转角度误差处理。
累计旋转角度超过360,我们需要对其取模转换,避免旋转角度误差持续累加,导致画面定位不准。rotateDegrees = rotateDegrees % 360
。Matrix
同步更新。
旋转处理VideoTouchRotateHandler
使用的变换矩阵Matrix
,如何与缩放处理VideoTouchScaleHandler
里面使用的Matrix
,进行同步更新?直接使用
matrix = textureVie.getTransform(null)
,就能获取到当前视频画面的变换矩阵,无论是缩放还是旋转,显然都是对同一个TextureView
来操作的,所以获取到是最新的值。这里有一个容易出错点,使用view.getMatrix
来获取Matrix
。当然根据实际需求,也可以自己缓存
Matrix
,只要能保证缩放和旋转同时获取到的是最新的Matrix
,并且能实时更新Matrix
给对方即可。旋转结束后补偿角度
rotateEndFixDegrees
的计算。/*** 计算旋转结束后需要补偿的角度* @param currentRotateDegree* @return*/public static float computeRoteEndDegree(float currentRotateDegree) {float rotateEndFixDegrees = currentRotateDegree % 90;if (rotateEndFixDegrees != 0) {if (rotateEndFixDegrees >= 45) { // 大于45度,直接旋转到90,计算旋转到90需要的角度rotateEndFixDegrees = 90 - rotateEndFixDegrees;} else if (rotateEndFixDegrees > -45 && rotateEndFixDegrees < 45) { // (-45, 45),回弹到0度位置rotateEndFixDegrees = -rotateEndFixDegrees;} else if (rotateEndFixDegrees < -45) { // 小于-45,直接旋转到-90,计算旋转到90需要的角度rotateEndFixDegrees = -90 - rotateEndFixDegrees;}}return rotateEndFixDegrees;}
旋转回弹动效结束后,更新实际角度位置。
旋转结束后,需要补偿旋转角度rotateEndFixDegrees
的,进行回弹动画,确保画面始终处于90度倍数位置,动画完成后,更新以前记录的旋转角度VideoTouchRotateHandler#mRotateDegrees
旋转处理:VideoTouchRotateHandler
/*** 手势旋转 处理* <p>** @author yinxuming* @date 2020/12/23*/
public class VideoTouchRotateHandler implements IVideoRotateHandler, RotateGestureDetector.OnRotateGestureListener {private static final String TAG = "VideoTouchRotateHandler";private IVideoTouchAdapter mTouchAdapter;private boolean mIsRotating; // 是否旋转中private float mRotateDegrees;private PointF mRotateCenter; // 目前需求只需要围绕画面中心旋转即可public VideoTouchRotateHandler(IVideoTouchAdapter videoTouchAdapter) {mTouchAdapter = videoTouchAdapter;}@Overridepublic boolean onRotateBegin(RotateGestureDetector detector) {if (isTextureViewValid()) {mTouchAdapter.getVideoTouchEndAnim().endPrevAnim();mIsRotating = true;mRotateCenter = new PointF(mTouchAdapter.getTextureView().getWidth() / 2,mTouchAdapter.getTextureView().getHeight() / 2);}return true;}@Overridepublic boolean onRotate(RotateGestureDetector detector, float degrees, float px, float py) {if (isRotating()) {postRotate(degrees); // 永远使用画面中心点进行旋转,避免由于旋转中心点引起位置变化,最后的回弹动效无法与边缘对齐}return true;}private void postRotate(float rotateDegree) {Matrix matrix = getTransformMatrix();matrix.postRotate(rotateDegree, mRotateCenter.x, mRotateCenter.y);updateMatrixToTexture(matrix);setRotateDegrees(getCurrentRotateDegree() + rotateDegree);}@Overridepublic void onRotateEnd(RotateGestureDetector detector) {LogUtil.e(TAG, "onRotateEnd " + mRotateDegrees);if (isRotating() && mTouchAdapter.getVideoTouchEndAnim() != null) {mTouchAdapter.getVideoTouchEndAnim().setEndAnimRotate(getCurrentRotateDegree(),computeRoteEndDegree(getCurrentRotateDegree()));}mIsRotating = false;}public boolean isRotating() {return mIsRotating;}@Overridepublic boolean isRotated() {return mRotateDegrees != 0 || mIsRotating;}@Overridepublic float getCurrentRotateDegree() {return mRotateDegrees;}private void setRotateDegrees(float rotateDegrees) {rotateDegrees = rotateDegrees % 360; // 大于360度的,取其模,避免累加误差急剧增大mRotateDegrees = rotateDegrees;}@Overridepublic float getTargetRotateDegree() {return getCurrentRotateDegree() + computeRoteEndDegree(getCurrentRotateDegree());}/*** 旋转回弹动画结束后,更新已补偿的角度* @param rotateDegree*/@Overridepublic void fixRotateEndAnim(float rotateDegree) {setRotateDegrees(getCurrentRotateDegree() + rotateDegree);}@Overridepublic void cancelRotate() {setRotateDegrees(0);mRotateCenter = null;}private boolean isTextureViewValid() {return mTouchAdapter.getTextureView() != null && mTouchAdapter.getTextureView().isAvailable();}private Matrix getTransformMatrix() {if (isTextureViewValid()) {return mTouchAdapter.getTextureView().getTransform(null);}return null;}private void updateMatrixToTexture(Matrix newMatrix) {if (isTextureViewValid()) {TextureView textureView = mTouchAdapter.getTextureView();textureView.setTransform(newMatrix);if (!mTouchAdapter.isPlaying()) {textureView.invalidate();}}}/*** 计算旋转结束后需要补偿的角度* @param currentRotateDegree* @return*/public static float computeRoteEndDegree(float currentRotateDegree) {float rotateEndFixDegrees = currentRotateDegree % 90;if (rotateEndFixDegrees != 0) {if (rotateEndFixDegrees >= 45) { // 大于45度,直接旋转到90,计算旋转到90需要的角度rotateEndFixDegrees = 90 - rotateEndFixDegrees;} else if (rotateEndFixDegrees > -45 && rotateEndFixDegrees < 45) { // (-45, 45),回弹到0度位置rotateEndFixDegrees = -rotateEndFixDegrees;} else if (rotateEndFixDegrees < -45) { // 小于-45,直接旋转到-90,计算旋转到90需要的角度rotateEndFixDegrees = -90 - rotateEndFixDegrees;}}return rotateEndFixDegrees;}}
3. 回弹动效
整个视频手势缩放、旋转、位移最难处理的部分,应该就是计算回弹动效了。实现回弹动效主要有两种思路:
回弹动效思路一
已知当前画面的位置startAnimMatrix
,要进行回弹动效到最终位置,主要做了2类操作,位移和旋转,也就是我们只需要计算出位移补偿和旋转角度补偿,就可以构造属性动画,进行线性渐变位移与旋转。实际在使用时,该方案有一些问题,缩小时不一定完全居中,推测可能是计算位移补偿时,是先进行旋转,再计算位移补偿,而实际使用时,动画是一边执行旋转,一边执行位移,导致位移的值不准确。回弹动效思路二
这里还有一种思路,参考自定义可旋转、平移、缩放的ImageView或PinchImageView。它的主要思路,也是已知startAnimMatrix
,然后关注点变成直接去计算出动效结束矩阵endAnimMatrix
,然后动画执行时去操作矩阵Matrix
上9个分量的值,使startAnimMatrix
最终达到endAnimMatrix
。
这里我们主要按思路二进行回弹动效实现,需要解决以下几个问题:
- 动效触发的时机
- 动效参数计算:旋转补偿角度引起
transAnimX
、transAnimY
的变化如何计算? - 如何处理连续动画:即本次动画还未执行完毕,下次动画已经到来,如何处理?
1. 动效触发时机
回弹动效的时机,原来是在缩放结束onScaleEnd
后,现在需要同时保证在缩放结束后onScaleEnd
和旋转结束后onRotateEnd
执行。下面我们观察下onTouchEvent
事件的回调顺序,可以得出结论,在ACTION_UP
中触发回弹动效即可。
双指触摸onTouchEvent回调顺序
通过观察事件触摸回调顺序,我们可以看到,只需要在ACTION_UP
中触发回弹动效即可。
int count = event.getPointerCount(); // 手指触摸点
int action = event.getActionMasked; // 触摸动作
onTouchEvent双指触摸事件回调顺序:
count | action | |
---|---|---|
1 | ACTION_DOWN | 单指按下 |
2 | ACTION_POINTER_DOWN | 第二只手指按下 |
2 | ACTION_MOVE | 触摸移动 |
2 | ACTION_MOVE | |
… | … | |
2 | ACTION_POINTER_UP |
抬起其中一只手指触发: onScaleEnd、onRotateEnd |
1 | ACTION_UP | 抬起最后一只手指触发 |
2. 动效参数计算
回弹动效参数主要涉及2类:
- 旋转补偿:补偿角度
rotateEndFixDegrees
、旋转中心(画面中心),这个上面已经知道如何计算 - 位移补偿:
transAnimX
、transAnimY
这里要解决的问题就是如何计算加入旋转后的位移补偿transAnimX
、transAnimY
?主要思路如下:
旋转结束后,画面的变换矩阵
currentTransformMatrix = textureView.getTransform(null)
是已知的,这个相当于是回弹动效的起始位置startAnimMatrix
要计算回弹动效结束位置矩阵
endAnimMatrix
,我们首先要对当前矩阵进行旋转角度补偿,即:currentTransformMatrix.postRotate(fixDegrees, center.x, center.y)
,得到endAnimMatrix
,此时画面的位置距离动画结束后的位置,差别就是:位移补偿,这时再计算transAnimX
、transAnimY
即可。transAnimX
、transAnimY
计算,我们同缩放一样,先通过endAnimMatrix.mapRect(rectF)
测量出上述旋转补偿后的矩形位置currentLocationRectF
,然后对比视频TextureView
所占屏幕矩形区域的videoRectF
位置(全屏情况下就是屏幕矩形区域,下面描述简化为对屏幕的描述),则可以计算出位移补偿。下面以
transAnimX
计算为例,具体的算法是:- 当变换后画面的宽度 < 屏幕宽度:画面居中。
- 当变换后画面左边界 在 屏幕内部:画面左边界吸附到屏幕左边缘
- 当变换后画面右边界 在 屏幕内部:画面右边界吸附到屏幕右边缘
if (currentLocationRectF.width() <= videoRectF.width()) { // 宽度 < 屏宽:居中transAnimX = videoRectF.right / 2 - (currentLocationRectF.right + currentLocationRectF.left) / 2;} else if (currentLocationRectF.left > videoRectF.left) { // 左侧在屏幕内:左移吸边transAnimX = videoRectF.left - currentLocationRectF.left;} else if (currentLocationRectF.right < videoRectF.right) { // 右侧在屏幕内:右移吸边transAnimX = videoRectF.right - currentLocationRectF.right;}
transAnimY
同理计算即可。
3. 连续回弹动效处理
动效的运行需要时间,如果上一次动效没有执行完毕,新的动效已经产生,我们该如何处理?
按照传统处理方式,我们需要执行animtor.cancel()
。但是这里显然不能这么做,属性动画ValueAnimotor.cancel
后,只会停留在当前位置,我们要的效果是,新动画准备好后,原有动画补偿的位移和角度都必须完成,即:立即执行到结束位置,这样才不至于影响新动画位置的计算。
这里我们直接使用ValueAnimotor.end
就可以达到这个效果,调用后,它直接会回调onAnimationUpdate
结束值和onAnimationEnd
。
但使用的时候,需要注意判断条件,启动后的动画才能执行end
,否则,动画结束后,调用end
,动画会重新运行一遍:onAnimationStart,onAnimationUpdate,onAnimationUpdate... onAnimationEnd
。
动画结束后,我们需要更新原有记录的旋转角度VideoTouchRotateHandler#mRotateDegrees
为最终角度。VideoTouchRotateHandler#fixRotateEndAnim
。
下面是动效的计算与执行的源码
回弹动效参数计算:VideoTouchFixEndAnim
/*** 回弹动效参数计算、动画状态控制* <p>** @author yinxuming* @date 2020/12/24*/
public class VideoTouchFixEndAnim implements IVideoTouchEndAnim {private IVideoTouchAdapter mTouchAdapter;private ValueAnimator mAnimator;float mScale = 1.0f;float mCurrentRotateDegrees;float mRotateEndFixDegrees;boolean isNeedFixAnim = false;public VideoTouchFixEndAnim(IVideoTouchAdapter touchAdapter) {mTouchAdapter = touchAdapter;}@Overridepublic void setEndAnimScale(float scale) {mScale = scale;isNeedFixAnim = true;}@Overridepublic void setEndAnimRotate(float currentRotate, float rotateEndFixDegrees) {mCurrentRotateDegrees = currentRotate;mRotateEndFixDegrees = rotateEndFixDegrees;isNeedFixAnim = true;}@Overridepublic void startAnim() {// 注意在主线程调用动画相关操作endPrevAnim();if (!isNeedFixAnim) {return;}mAnimator = makeFixEndAnimator();if (mAnimator == null) {return;}mAnimator.start();}@Overridepublic void endPrevAnim() {if (mAnimator != null && (mAnimator.isRunning() || mAnimator.isStarted())) {mAnimator.end();}mAnimator = null;}/*** 计算transAnimX、transAnimY 得到endAnimMatrix,生成动画对象* @return*/private ValueAnimator makeFixEndAnimator() {TextureView mTextureView = mTouchAdapter.getTextureView();// 动画 start矩阵:当前画面变换Matrix currentTransformMatrix = mTextureView.getTransform(null);Matrix endAnimMatrix = new Matrix();final float fixDegrees = mRotateEndFixDegrees;RectF videoRectF = new RectF(0, 0, mTextureView.getWidth(), mTextureView.getHeight());PointF center = new PointF(videoRectF.right / 2, videoRectF.bottom / 2);endAnimMatrix.set(currentTransformMatrix);// 动画 end矩阵:模拟计算当前画面经过旋转补偿后的矩阵endAnimMatrix.postRotate(fixDegrees, center.x, center.y);RectF currentLocationRectF = new RectF(0, 0, mTextureView.getWidth(), mTextureView.getHeight());// 测量画面最终应该进行的矩阵变换位置endAnimMatrix.mapRect(currentLocationRectF);float transAnimX = 0f;float transAnimY = 0f;if (currentLocationRectF.left > videoRectF.left|| currentLocationRectF.right < videoRectF.right|| currentLocationRectF.top > videoRectF.top|| currentLocationRectF.bottom < videoRectF.bottom) { //,有一边缩放后在屏幕内部,自动吸附到屏幕边缘 或 居中if (currentLocationRectF.width() <= videoRectF.width()) { // 宽度 < 屏宽:居中transAnimX = videoRectF.right / 2 - (currentLocationRectF.right + currentLocationRectF.left) / 2;} else if (currentLocationRectF.left > videoRectF.left) { // 左侧在屏幕内:左移吸边transAnimX = videoRectF.left - currentLocationRectF.left;} else if (currentLocationRectF.right < videoRectF.right) { // 右侧在屏幕内:右移吸边transAnimX = videoRectF.right - currentLocationRectF.right;}if (currentLocationRectF.height() <= videoRectF.height()) { // 高度 < 屏搞:居中transAnimY = videoRectF.bottom / 2 - (currentLocationRectF.bottom + currentLocationRectF.top) / 2;} else if (currentLocationRectF.top > videoRectF.top) { // 上移吸边transAnimY = videoRectF.top - currentLocationRectF.top;} else if (currentLocationRectF.bottom < videoRectF.bottom) { // 下移吸边transAnimY = videoRectF.bottom - currentLocationRectF.bottom;}}endAnimMatrix.postTranslate(transAnimX, transAnimY);// 不使用动画直接变换
// mTouchAdapter.getTextureView().setTransform(endAnimMatrix);
// mTouchAdapter.getVideoRotateHandler().postRotateDegrees(fixDegrees, false);if (transAnimX == 0 && transAnimY == 0 && fixDegrees == 0) {return null;} else {ScaleRotateEndAnimator animator = new ScaleRotateEndAnimator() {@Overrideprotected void updateMatrixToView(Matrix transMatrix) {mTouchAdapter.getTextureView().setTransform(transMatrix);}@Overrideprotected void onFixEndAnim(ValueAnimator animator, float fixEndDegrees) {mTouchAdapter.getVideoRotateHandler().fixRotateEndAnim(fixEndDegrees);if (animator == mAnimator) {mAnimator = null;onAnimEndRelease();}}};animator.setScaleEndAnimParams(currentTransformMatrix, endAnimMatrix, fixDegrees);return animator;}}private void onAnimEndRelease() {isNeedFixAnim = false;mScale = 1.0f;mCurrentRotateDegrees = 0;mRotateEndFixDegrees = 0;}
}
回弹动效执行:ScaleRotateEndAnimator
public abstract class ScaleRotateEndAnimator extends ValueAnimator implements ValueAnimator.AnimatorUpdateListener,Animator.AnimatorListener {private static final String TAG = "VideoScaleEndAnimator";/*** 图片缩放动画时间*/public static final int SCALE_ANIMATOR_DURATION = 1000;private Matrix mStartMatrix = new Matrix();private Matrix mEndMatrix = new Matrix();private Matrix mMatrix = new Matrix();private float[] mStartMatrixValue;private float[] mInterpolateMatrixValue;private float[] mEndMatrixValue;private float mRotateDegrees;public void setScaleEndAnimParams(Matrix startMatrix, Matrix endMatrix, float rotateFixDegree) {mStartMatrix = startMatrix;mEndMatrix = endMatrix;mRotateDegrees = rotateFixDegree;mMatrix.reset();if (mStartMatrix == null || mEndMatrix == null) {return;}mStartMatrixValue = new float[9];mStartMatrix.getValues(mStartMatrixValue);mEndMatrixValue = new float[9];mEndMatrix.getValues(mEndMatrixValue);mInterpolateMatrixValue = new float[9];setAnimConfig();}protected void setAnimConfig() {setFloatValues(0, 1f);setDuration(SCALE_ANIMATOR_DURATION);addUpdateListener(this);addListener(this);}@Overridepublic void onAnimationUpdate(ValueAnimator animation) {// 获取动画进度float value = (Float) animation.getAnimatedValue();onValueUpdate(value);}public void onValueUpdate(float value) {if (mStartMatrix == null|| mEndMatrix == null) {return;}for (int i = 0; i < 9; i++) {mInterpolateMatrixValue[i] = mStartMatrixValue[i] + (mEndMatrixValue[i] - mStartMatrixValue[i]) * value;}mMatrix.setValues(mInterpolateMatrixValue);updateMatrixToView(mMatrix);}protected abstract void updateMatrixToView(Matrix transMatrix);protected abstract void onFixEndAnim(ValueAnimator animator, float fixEndDegrees);@Overridepublic void onAnimationStart(Animator animation) {}@CallSuper@Overridepublic void onAnimationEnd(Animator animation) {onFixEndAnim(this, mRotateDegrees);}@CallSuper@Overridepublic void onAnimationCancel(Animator animation) {}@Overridepublic void onAnimationRepeat(Animator animation) {}}
项目完整代码
github完整源码
参考
- Android Matrix 带你掌控雷电
- 自定义可旋转、平移、缩放的ImageView
Android 视频旋转、缩放与回弹动效实现(二)相关推荐
- Android 视频手势缩放与回弹动效实现(一)
文章目录 Android 视频手势缩放与回弹动效实现(一) 1. 功能需求 2. 实现原理 2.1 如何检测手势缩放? 1. View.onTouchEvent关键代码 2. ScaleGesture ...
- android属性动画 呼吸,【MIUI动效】Android:会呼吸的悬浮气泡
原标题:[MIUI动效]Android:会呼吸的悬浮气泡 写在前面 这个标题看起来玄乎玄乎的,其实一张图就明白了: 悬浮气泡演示图 最早看到这个效果是 MIUI6系统升级界面,有很多五颜六色的气泡悬浮 ...
- Android视频编辑SDK--RDVECore来自锐动的无UI,高度抽象化API
1 RDVECore功能概述 RDVECore是锐动推出的无UI,高度抽象化API的视频编辑SDK,支持以下功能: 1.1 丰富的编辑功能 RDVECore包含了丰富的基础功能,对于编辑中的视 ...
- android视频旋转处理方法
这几天在写视频播放器,采用surfaceview搭配mediaplayer或者VideoView进行视频播放,一切都还顺风顺水,当我播放一个方向不对的视频的时候没能自动转换成正确的方向.这时只能靠自己 ...
- Android开发之自定义SurfaceView绘制动效音波图 | 动效音阶图 | Android自定义View
老套路献上图: 第一张是通过播放歌曲拿到歌曲播放的数据进行动态展示的 第二张是通过定时器随机生成的数据动态展示的 先说下这个自定义view也不难很简单,就是绘制矩形,唯一的难点在于计算矩形的坐标 说下 ...
- android 融球效果图,加载动效制作(普及一些小常识)+刷人气^_^_html/css_WEB-ITnose...
表达式是大神的标配,熟练使用关键帧也可以棒棒哒! 一直想写个小教程来把自己在使用AE过程遇到的问题.怎么解决的过程表达出来,当问题被解决的那一刻,做的东东过关那一刻就有好激动有木有... 闲话少说直接 ...
- html5动效系列二:超级惊艳 10款HTML5动画特效推荐
文章来源:http://www.lanlanwork.com/blog/?post=2684 蓝蓝设计( www.lanlanwork.com )是一家专注而深入的界面设计公司,为期望卓越的国内外企业 ...
- [jQuery基础] jQuery动效案例(二) -- 图标特效、无限循环滚动(简易轮播图)
图标特效 实现效果展示 实现步骤 第一步(实现静态效果) CSS部分 *{margin: 0;padding: 0; } ul{list-style: none;width: 400px;height ...
- 拒绝枯燥,有意思的 Loading 页面动效设计
2019独角兽企业重金招聘Python工程师标准>>> 互联网时代,网络"提速"日益频繁,人们打开Web或软件的速度越来越快,一般页面缓冲和加载地过程也是几不可查 ...
最新文章
- 学python多长时间能够精通-Python培训需要多长时间可以学会?
- 近邻取样插值和其速度优化
- Resnet的pytorch官方实现代码解读
- 计算机组成原理译码器选择,计算机组成原理第三章习题参考解析.doc
- 飞桨 第一课 传统图像识别是怎么做的+Aistudio python数据可视化2020.3.31;2020.4.2补
- 5、Linux内核模块开发
- centos 6.9 NTP基准时间服务器配置
- java jdbc封装_JDBC封装-Java(新手)
- 自定义数据格式的矢量地图实现
- [源码]C# to SQL 的翻译器.net 1.1版
- 自由落体运动c语言编程_欧姆龙NX PLC 轴运动功能块,ST和梯形图双语言
- 全网最详细的纪录片观看&下载指南
- c语言系统主函数流程图,c语言流程图【调解方式】
- android 选座系统,android 影院选座
- java poi Excel加密文件导出和下载
- java文字云_在线文字云制作工具
- 关于人际关系的一些问答
- Games101计算机图形学学习笔记:线性代数-向量
- Google最热门60款开源项目
- 工业机器人 郝卫东_川崎焊接机器人控制系统设计开发
热门文章
- 【寄存器模型】二、前门访问与后门访问
- APP内嵌H5开发常见问题及解决方案
- 2020 China Collegiate Programming Contest, Weihai B Labyrinth
- NAT和代理服务器的原理及应用
- [工作积累] 大型世界的草渲染
- 牛客Mysql——SQL必知必会
- 曹颖生日会带货直播近5600万,一周年交出漂亮成绩单
- Python—wordcloud(pip安装失败问题解决)
- lisp删除块中图元_删除块定义以及删除块参照|二次开发objectarx-lisp|中国膜结构网|PVDF|ETFE|PTFE|进口膜材|国产膜材|膜材价格 - 中国最专业的膜结构论坛...
- IDEA插件系列(35):Fancy Music插件——背景音乐插件