文章目录

  • Android 视频旋转、缩放与回弹动效实现(二)
  • 功能需求
  • 实现思路
    • 1. 旋转识别
      • 旋转识别:RotateGestureDetector
    • 2. 旋转处理
      • 旋转处理:VideoTouchRotateHandler
    • 3. 回弹动效
      • 1. 动效触发时机
        • 双指触摸onTouchEvent回调顺序
      • 2. 动效参数计算
      • 3. 连续回弹动效处理
      • 回弹动效参数计算:VideoTouchFixEndAnim
      • 回弹动效执行:ScaleRotateEndAnimator
  • 项目完整代码
  • 参考

文章索引

  1. Android 视频手势缩放与回弹动效实现(一):主要是实现视频双指:缩放、平移、回弹动效
  2. Android 视频旋转、缩放与回弹动效实现(二):主要是实现视频双指:旋转、缩放、平移、回弹动效

Android 视频旋转、缩放与回弹动效实现(二)

在Android 视频手势缩放与回弹动效实现(一)中我们实现了下列1-5部分的需求,事实上对于双指手势触摸,不仅可以缩放、平移,还可以进行旋转,现在我们就在原有基础上进行改造,添加视频手势旋转回弹动效,来实现需求6。

功能需求

  1. 双指缩放视频播放画面,支持设定最小、最大缩放范围
  2. 双指拖动画面可任意方向移动
  3. 如果是缩小画面,最后需要在屏幕居中显示,并且需要有动画效果
  4. 如果是放大画面,有画面边缘在屏幕内的,需要自动吸附到屏幕边缘
  5. 视频暂停状态下也能缩放
  6. 双指旋转画面,旋转角度以45度为分界线,进行自动校正(超过45度旋转90度;不超过45度旋转0度)

实现思路

实现主要思路

  1. 手势旋转识别。
    接收onTouchEvent事件,识别双指旋转:旋转角度、旋转中心
  2. 手势旋转处理。
    识别到手势旋转后,通过Matrix.postRotate进行画面旋转变换
  3. 缩放倍数逻辑改造。
    由于旋转也会引起Matrix#MSCALE_X值发生变化,缩放倍数不能再通过直接获取矩阵该分量值来计算,需要将计算后的缩放倍数独立保存。参考:Android Matrix 带你掌控雷电,旋转部分对Matrix的影响
  4. 回弹动效触发。
    原有回弹动效是在缩放结束后onScaleEnd触发,加入旋转后,需要保证在缩放结束onScaleEnd和旋转结束onRotateEnd后才触发,这里选择ACTION_UP来触发回弹动效。
  5. 回弹动效的计算。
    加入旋转后,回弹动效在原有2个数据:transAnimXtransAnimY(x、y轴上的平移补偿)基础上增加了旋转补偿角度rotateEndFixDegrees

1. 旋转识别

在缩放、平移中我们使用系统自带缩放识别类ScaleGestureDetector.onTouchEvent(event)来识别当前触摸事件,得到onScaleBeginonScaleonScaleEnd的缩放回调,然后用VideoTouchScaleHandler来对缩放、平移进行相应处理。

加入旋转功能后,由于系统没有自带旋转识别类,我们需要自定义实现手势识别旋转:RotateGestureDetector。下面介绍RotateGestureDetector里面的主要逻辑,即如何识别旋转中心旋转角度

  1. 旋转中心
    旋转中心计算比较简单,通过获取双指间连线的中心点即可。
    但是实际我们在视频画面旋转中,不会使用双指中心来进行旋转,而是直接使用画面中心来旋转,为什么呢?

    如果采用双指中心来旋转,因为每次旋转的中心点都不固定,会造成旋转时,变换矩阵中的位移分量也受到了影响,这样后续在计算回弹动效的时候,需要补偿的位移transAnimXtransAnimY会因为旋转中心点变化,而无法准确计算出。

     // 计算双指旋转中心点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);
    
  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​。几个注意的小点:

  1. Math.atan2(y, x)Math.atan(v)差异。

    都能计算反正切,为了避免除数为0的情况(当然还有其它差别,如结果范围不同),我们一般选用Math.atan2(y, x)

  2. Math.atan2(y, x)结果。

    计算结果值为弧度,取值范围**[-Math.PI, Math.PI],由于旋转操作Matrix.postRotate传入的参数为具体角度,所以我们需要将弧度通过Math.toDegrees转换为角度** [-180, 180]。

  3. 分母微小抖动带来的角度剧烈变化

    试想一下 Δ y Δ x \frac{\Delta y}{\Delta x} ΔxΔy​,当 Δ y \Delta y Δy一定情况下, Δ x \Delta x Δx轴偏移量稍微变化点点,都会引起角度剧烈变化,显然不符合用户的真实意图,所以对于超过45度的变化,我们可以认为只改变少许角度,这里取值正负5度。

  4. 旋转角度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)来对画面进行旋转。几个注意点:

  1. Matrix.postRotate(degrees, px, py)Matrix.postRotate(degrees)区别。
    不带旋转中心参数的方法,默认旋转中心是控件本身左上角,所以我们必须使用带旋转中心参数的方法,设置旋转中心为TextureView中心点位置。

  2. 累计旋转角度误差处理。
    累计旋转角度超过360,我们需要对其取模转换,避免旋转角度误差持续累加,导致画面定位不准。rotateDegrees = rotateDegrees % 360

  3. Matrix同步更新。
    旋转处理VideoTouchRotateHandler使用的变换矩阵Matrix,如何与缩放处理VideoTouchScaleHandler里面使用的Matrix,进行同步更新?

    直接使用matrix = textureVie.getTransform(null),就能获取到当前视频画面的变换矩阵,无论是缩放还是旋转,显然都是对同一个TextureView来操作的,所以获取到是最新的值。这里有一个容易出错点,使用view.getMatrix来获取Matrix

    当然根据实际需求,也可以自己缓存Matrix,只要能保证缩放和旋转同时获取到的是最新的Matrix,并且能实时更新Matrix给对方即可。

  4. 旋转结束后补偿角度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;}
    
  5. 旋转回弹动效结束后,更新实际角度位置。
    旋转结束后,需要补偿旋转角度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. 回弹动效

整个视频手势缩放、旋转、位移最难处理的部分,应该就是计算回弹动效了。实现回弹动效主要有两种思路:

  1. 回弹动效思路一
    已知当前画面的位置startAnimMatrix,要进行回弹动效到最终位置,主要做了2类操作,位移旋转,也就是我们只需要计算出位移补偿旋转角度补偿,就可以构造属性动画,进行线性渐变位移与旋转。实际在使用时,该方案有一些问题,缩小时不一定完全居中,推测可能是计算位移补偿时,是先进行旋转,再计算位移补偿,而实际使用时,动画是一边执行旋转,一边执行位移,导致位移的值不准确。

  2. 回弹动效思路二
    这里还有一种思路,参考自定义可旋转、平移、缩放的ImageView或PinchImageView。它的主要思路,也是已知startAnimMatrix,然后关注点变成直接去计算出动效结束矩阵endAnimMatrix,然后动画执行时去操作矩阵Matrix上9个分量的值,使startAnimMatrix最终达到endAnimMatrix

这里我们主要按思路二进行回弹动效实现,需要解决以下几个问题:

  1. 动效触发的时机
  2. 动效参数计算:旋转补偿角度引起transAnimXtransAnimY的变化如何计算?
  3. 如何处理连续动画:即本次动画还未执行完毕,下次动画已经到来,如何处理?

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类:

  1. 旋转补偿:补偿角度rotateEndFixDegrees、旋转中心(画面中心),这个上面已经知道如何计算
  2. 位移补偿:transAnimXtransAnimY

这里要解决的问题就是如何计算加入旋转后的位移补偿transAnimXtransAnimY?主要思路如下:

  1. 旋转结束后,画面的变换矩阵currentTransformMatrix = textureView.getTransform(null)是已知的,这个相当于是回弹动效的起始位置startAnimMatrix

  2. 要计算回弹动效结束位置矩阵endAnimMatrix,我们首先要对当前矩阵进行旋转角度补偿,即:currentTransformMatrix.postRotate(fixDegrees, center.x, center.y),得到endAnimMatrix,此时画面的位置距离动画结束后的位置,差别就是:位移补偿,这时再计算transAnimXtransAnimY即可。

  3. transAnimXtransAnimY计算,我们同缩放一样,先通过endAnimMatrix.mapRect(rectF)测量出上述旋转补偿后的矩形位置currentLocationRectF,然后对比视频TextureView所占屏幕矩形区域的videoRectF位置(全屏情况下就是屏幕矩形区域,下面描述简化为对屏幕的描述),则可以计算出位移补偿。

    下面以transAnimX计算为例,具体的算法是:

    1. 当变换后画面的宽度 < 屏幕宽度:画面居中
    2. 当变换后画面左边界 在 屏幕内部:画面左边界吸附到屏幕左边缘
    3. 当变换后画面右边界 在 屏幕内部:画面右边界吸附到屏幕右边缘
    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完整源码

参考

  1. Android Matrix 带你掌控雷电
  2. 自定义可旋转、平移、缩放的ImageView

Android 视频旋转、缩放与回弹动效实现(二)相关推荐

  1. Android 视频手势缩放与回弹动效实现(一)

    文章目录 Android 视频手势缩放与回弹动效实现(一) 1. 功能需求 2. 实现原理 2.1 如何检测手势缩放? 1. View.onTouchEvent关键代码 2. ScaleGesture ...

  2. android属性动画 呼吸,【MIUI动效】Android:会呼吸的悬浮气泡

    原标题:[MIUI动效]Android:会呼吸的悬浮气泡 写在前面 这个标题看起来玄乎玄乎的,其实一张图就明白了: 悬浮气泡演示图 最早看到这个效果是 MIUI6系统升级界面,有很多五颜六色的气泡悬浮 ...

  3. Android视频编辑SDK--RDVECore来自锐动的无UI,高度抽象化API

    1    RDVECore功能概述 RDVECore是锐动推出的无UI,高度抽象化API的视频编辑SDK,支持以下功能: 1.1 丰富的编辑功能  RDVECore包含了丰富的基础功能,对于编辑中的视 ...

  4. android视频旋转处理方法

    这几天在写视频播放器,采用surfaceview搭配mediaplayer或者VideoView进行视频播放,一切都还顺风顺水,当我播放一个方向不对的视频的时候没能自动转换成正确的方向.这时只能靠自己 ...

  5. Android开发之自定义SurfaceView绘制动效音波图 | 动效音阶图 | Android自定义View

    老套路献上图: 第一张是通过播放歌曲拿到歌曲播放的数据进行动态展示的 第二张是通过定时器随机生成的数据动态展示的 先说下这个自定义view也不难很简单,就是绘制矩形,唯一的难点在于计算矩形的坐标 说下 ...

  6. android 融球效果图,加载动效制作(普及一些小常识)+刷人气^_^_html/css_WEB-ITnose...

    表达式是大神的标配,熟练使用关键帧也可以棒棒哒! 一直想写个小教程来把自己在使用AE过程遇到的问题.怎么解决的过程表达出来,当问题被解决的那一刻,做的东东过关那一刻就有好激动有木有... 闲话少说直接 ...

  7. html5动效系列二:超级惊艳 10款HTML5动画特效推荐

    文章来源:http://www.lanlanwork.com/blog/?post=2684 蓝蓝设计( www.lanlanwork.com )是一家专注而深入的界面设计公司,为期望卓越的国内外企业 ...

  8. [jQuery基础] jQuery动效案例(二) -- 图标特效、无限循环滚动(简易轮播图)

    图标特效 实现效果展示 实现步骤 第一步(实现静态效果) CSS部分 *{margin: 0;padding: 0; } ul{list-style: none;width: 400px;height ...

  9. 拒绝枯燥,有意思的 Loading 页面动效设计

    2019独角兽企业重金招聘Python工程师标准>>> 互联网时代,网络"提速"日益频繁,人们打开Web或软件的速度越来越快,一般页面缓冲和加载地过程也是几不可查 ...

最新文章

  1. 学python多长时间能够精通-Python培训需要多长时间可以学会?
  2. 近邻取样插值和其速度优化
  3. Resnet的pytorch官方实现代码解读
  4. 计算机组成原理译码器选择,计算机组成原理第三章习题参考解析.doc
  5. 飞桨 第一课 传统图像识别是怎么做的+Aistudio python数据可视化2020.3.31;2020.4.2补
  6. 5、Linux内核模块开发
  7. centos 6.9 NTP基准时间服务器配置
  8. java jdbc封装_JDBC封装-Java(新手)
  9. 自定义数据格式的矢量地图实现
  10. [源码]C# to SQL 的翻译器.net 1.1版
  11. 自由落体运动c语言编程_欧姆龙NX PLC 轴运动功能块,ST和梯形图双语言
  12. 全网最详细的纪录片观看&下载指南
  13. c语言系统主函数流程图,c语言流程图【调解方式】
  14. android 选座系统,android 影院选座
  15. java poi Excel加密文件导出和下载
  16. java文字云_在线文字云制作工具
  17. 关于人际关系的一些问答
  18. Games101计算机图形学学习笔记:线性代数-向量
  19. Google最热门60款开源项目
  20. 工业机器人 郝卫东_川崎焊接机器人控制系统设计开发

热门文章

  1. 【寄存器模型】二、前门访问与后门访问
  2. APP内嵌H5开发常见问题及解决方案
  3. 2020 China Collegiate Programming Contest, Weihai B Labyrinth
  4. NAT和代理服务器的原理及应用
  5. [工作积累] 大型世界的草渲染
  6. 牛客Mysql——SQL必知必会
  7. 曹颖生日会带货直播近5600万,一周年交出漂亮成绩单
  8. Python—wordcloud(pip安装失败问题解决)
  9. lisp删除块中图元_删除块定义以及删除块参照|二次开发objectarx-lisp|中国膜结构网|PVDF|ETFE|PTFE|进口膜材|国产膜材|膜材价格 - 中国最专业的膜结构论坛...
  10. IDEA插件系列(35):Fancy Music插件——背景音乐插件