Android 視頻手勢縮放與回彈動效實現(一)

[toc]java

文章索引git

  1. Android 視頻手勢縮放與回彈動效實現(一):主要是實現視頻雙指:縮放、平移、回彈動效
  2. Android 視頻旋轉、縮放與回彈動效實現(二):主要是實現視頻雙指:旋轉、縮放、平移、回彈動效

1. 功能需求

  1. 雙指縮放視頻播放畫面,支持設定最小、最大縮放範圍
  2. 雙指拖動畫面可任意方向移動
  3. 若是是縮小畫面,最後須要在屏幕居中顯示,而且須要有動畫效果
  4. 若是是放大畫面,有畫面邊緣在屏幕內的,須要自動吸附到屏幕邊緣
  5. 視頻暫停狀態下也能縮放

2. 實現原理

  1. 先進行縮放平移。 經過View.getMatrix()獲取當前播放畫面的Matrix,進行矩陣變換:縮放、平移,改變畫面位置和大小,實現播放畫面縮放功能。
  2. 縮放結束後,進行屬性動畫。 當前畫面對應的矩陣變換爲mScaleTransMatrix,計算動畫結束應該移動的位scaleEndAnimMatrix,進行屬性動畫從mScaleTransMatrix變化爲scaleEndAnimMatrix

2.1 如何檢測手勢縮放?

  1. View.onTouchEvent。分別監聽手指按下(MotionEvent.ACTION_POINTER_DOWN)、擡起(MotionEvent.ACTION_POINTER_UP)、移動(MotionEvent.ACTION_MOVE
  2. ScaleGestureDetector。直接使用手勢縮放檢測ScaleGestureDetector對View#onTouchEvent中的手勢變化進行識別,經過ScaleGestureDetector.OnScaleGestureListener獲得onScaleBegin-onScale-onScale ... -onScaleEnd的縮放回調,在回調中處理響應的縮放邏輯。

1. View.onTouchEvent關鍵代碼

public boolean onTouchEvent(MotionEvent event) {
    int action = event.getAction() & MotionEvent.ACTION_MASK;
    switch (action) {
        case MotionEvent.ACTION_POINTER_DOWN:
            onScaleBegin(event);
            break;
        case MotionEvent.ACTION_POINTER_UP:
            onScaleEnd(event);
            break;
        case MotionEvent.ACTION_MOVE:
            onScale(event);
            break;
        case MotionEvent.ACTION_CANCEL:
            cancelScale(event);
            break;
    }
    return true;
}
複製代碼

2. ScaleGestureDetector

使用ScaleGestureDetector來識別onTouchEvent中的手勢觸摸操做,獲得onScaleBeginonScaleonScaleEnd三種回調,在回調裏面經過VideoTouchScaleHandler對視頻進行縮放、平移操做。github

  1. 添加手勢觸摸層GestureLayer,使用ScaleGestureDetector識別手勢緩存

    /** * 手勢處理layer層 */
    public final class GestureLayer implements IGestureLayer, GestureDetector.OnGestureListener, GestureDetector.OnDoubleTapListener {
        private static final String TAG = "GestureLayer";
    
        private Context mContext;
        private FrameLayout mContainer;
    
        /** 手勢檢測 */
        private GestureDetector mGestureDetector;
    
        /** 手勢縮放 檢測 */
        private ScaleGestureDetector mScaleGestureDetector;
        /** 手勢縮放 監聽 */
        private VideoScaleGestureListener mScaleGestureListener;
        /** 手勢縮放 處理 */
        private VideoTouchScaleHandler mScaleHandler;
        private IVideoTouchAdapter mVideoTouchAdapter;
    
        public GestureLayer(Context context, IVideoTouchAdapter videoTouchAdapter) {
            mContext = context;
            mVideoTouchAdapter = videoTouchAdapter;
            initContainer();
            initTouchHandler();
        }
    
        private void initContainer() {
            mContainer = new FrameLayout(mContext) {
                @Override
                public boolean dispatchTouchEvent(MotionEvent ev) {
                    return super.dispatchTouchEvent(ev);
                }
    
                @Override
                public boolean onInterceptTouchEvent(MotionEvent ev) {
                    return super.onInterceptTouchEvent(ev);
                }
    
                @Override
                public boolean onTouchEvent(MotionEvent event) {
                    boolean isConsume = onGestureTouchEvent(event);
                    if (isConsume) {
                        return true;
                    } else {
                        return super.onTouchEvent(event);
                    }
                }
            };
        }
    
        public void initTouchHandler() {
            mGestureDetector = new GestureDetector(mContext, this);
            mGestureDetector.setOnDoubleTapListener(this);
    
            // 手勢縮放
            mScaleGestureListener = new VideoScaleGestureListener(this);
            mScaleGestureDetector = new ScaleGestureDetector(getContext(), mScaleGestureListener);
    
            // 縮放 處理
            mScaleHandler = new VideoTouchScaleHandler(getContext(), mContainer, mVideoTouchAdapter);
            mScaleGestureListener.mScaleHandler = mScaleHandler;
    
        }
    
        @Override
        public void onLayerRelease() {
            if (mGestureDetector != null) {
                mGestureDetector.setOnDoubleTapListener(null);
            }
        }
    
        @Override
        public boolean onGestureTouchEvent(MotionEvent event) {
            try {
                int pointCount = event.getPointerCount();
                if (pointCount == 1 && event.getAction() == MotionEvent.ACTION_UP) {
                    if (mScaleHandler.isScaled()) {
                        mScaleHandler.showScaleReset();
                    }
                }
                if (pointCount > 1) {
                    boolean isConsume = mScaleGestureDetector.onTouchEvent(event);
                    if (isConsume) {
                        return true;
                    }
                }
            } catch (Exception e) {
                Log.e(TAG, "", e);
            }
    
            if (event.getAction() == MotionEvent.ACTION_DOWN) {
                return true;
            }
            return false;
        }
    
    ...
    }
    複製代碼
  2. ScaleGestureDetector.OnScaleGestureListener 手勢縮放回調處理markdown

    /** * 手勢縮放 播放畫面 */
    public class VideoScaleGestureListener implements ScaleGestureDetector.OnScaleGestureListener {
        private static final String TAG = "VideoScaleGestureListener";
        private IGestureLayer mGestureLayer;
        public VideoTouchScaleHandler mScaleHandler;
    
        public VideoScaleGestureListener(IGestureLayer gestureLayer) {
            mGestureLayer = gestureLayer;
        }
    
        @Override
        public boolean onScale(ScaleGestureDetector detector) {
            if (mScaleHandler != null) {
                return mScaleHandler.onScale(detector);
            }
            return false;
        }
    
        @Override
        public boolean onScaleBegin(ScaleGestureDetector detector) {
            if (mScaleHandler != null) {
                boolean isConsume = mScaleHandler.onScaleBegin(detector);
                if (isConsume) {
                    return true;
                }
            }
            return true;
        }
    
        @Override
        public void onScaleEnd(ScaleGestureDetector detector) {
            if (mScaleHandler != null) {
                mScaleHandler.onScaleEnd(detector);
            }
    
        }
    }
    複製代碼

2.2 縮放平移處理

  1. 雙指縮放 使用Matrix.postScale(float sx, float sy, float px, float py),這裏有幾個參數,前兩個指定x,y軸上的縮放倍數,後兩個指定縮放中心點位置。
    • 如何計算縮放倍數? 本次縮放倍數 = 本次兩指間距 / 上次兩指間距:currentDiffScale = detector.getCurrentSpan() / mLastSpan
    • 如何肯定縮放中心點? 縮放中心爲兩指開始觸摸時的中心位置點,即onScaleBegin時,scaleCenterX = detector.getFocusX(); scaleCenterY = detector.getFocusY();
    • postXXXpreXXX的區別? postXXX爲右乘,preXXX爲前乘。出現這兩種操做,主要是矩陣乘法不知足交換律,實際使用過程當中,固定選擇一種方式便可。爲了方便理解,直接來段代碼,令:原矩陣M,位移變換矩陣T(x, y),則:
      M.postTranslate(tx, ty); // 等價 M' = T * M
      M.preTranslate(tx, ty); // 等價 M' = M * T
      複製代碼
  2. 雙指平移 雙指可平移拖動畫面到新位置,平移使用:`Matrix.postTranslate(float dx, float dy)

`,dx和dy表示相對當前的Matrix的位置須要移動的距離,注意必定是相對於當前的Matrix位置,而不是相對onScaleBegin時的Matrix初始位置。ide

  • 如何肯定平移距離
本次移動距離 = 本次中心點 - 上次中心點
```java
 dx = detector.getFocusX() - mLastCenterX
 dy = detector.getFocusY() - mLastCenterY
```
複製代碼

2.3 暫停畫面下縮放

默認不處理,暫停畫面狀況下,Matrix變換後,更新到TextureView上,畫面是不會發生變化的,要想畫面實時更新,調用TextureView.invalidate()便可。oop

2.4 縮放移動結束後動效

縮放結束後(onScaleEnd),爲了加強交互體驗,須要根據縮放的大小、位置,從新調整畫面,動畫移動到指定位置。指定位置主要有居中吸附屏幕邊緣兩種。 動畫的移動,主要採用屬性動畫ValueAnimator.post

1. 縮小居中

縮放結束後,畫面若是處於縮小模式,須要將畫面移動到屏幕中央。動畫

  1. 如何計算居中位置矩陣變換值? 縮放位移結束後獲得變換後的矩陣mScaleTransMatrix,這也是動畫的起始值,如今要推導動畫的結束位置矩陣scaleEndAnimMatrix,要求在屏幕中居中,若是要直接用mScaleTransMatrix進行變換獲得動畫結束矩陣, 須要在xy上平移必定距離,可是該距離具體指並很差計算。 這裏咱們從另外一個方向下手,知道當前的縮放倍速mScale,視頻TextureView佔的區域,那麼直接以該區域中心點進行矩陣縮放變化,就能夠獲得中心位置矩陣scaleEndAnimMatrix
    RectF videoRectF = new RectF(0, 0, mTextureView.getWidth(), mTextureView.getHeight());
     if (mScale > 0 && mScale <= 1.0f) { // 縮小居中
         scaleEndAnimMatrix.reset();
         scaleEndAnimMatrix.postScale(mScale, mScale, videoRectF.right / 2, videoRectF.bottom / 2);
     }
    複製代碼
  2. 屬性動畫中間值,如何獲得中間位置變換矩陣?
    • 動畫開始矩陣:mScaleTransMatrix
    • 動畫開始矩陣:scaleEndAnimMatrix
    當從mScaleTransMatrix動畫移動到scaleEndAnimMatrix位置時,中間的矩陣無非就是在x、y上位移了必定距離。以x軸爲例:
    1. x軸總位移:totalTransX = scaleEndAnimMatrix矩陣中取出MTRANS_X份量值 - mScaleTransMatrix矩陣中取出MTRANS_X份量值
    2. 本次x軸移動距離:transX = totalTransX * 本次動畫變化值 = totalTransX * (animation.getAnimatedValue() - mLastValue);

2. 放大吸邊

縮放結束後,若是畫面處於放大,且有畫面邊緣在屏幕內的,須要自動吸附到屏幕邊緣。this

  1. 如何判斷是否有畫面邊緣在屏幕內部? 須要考慮四邊:left、top、right、bottom位置的狀況。若是要考慮畫面在屏幕內部的總狀況數,比較繁瑣和複雜,好比以left爲例:有3種狀況:

    1. left:僅left邊在屏幕內部,top、bottom邊在屏幕外部,只須要移動畫面left邊到屏幕左邊便可
    2. left + top:left邊和top邊在屏幕內部,須要移動畫面到屏幕左上角頂點位置
    3. left + bottom:同上,須要移動畫面到屏幕左下角頂點位置

    總共有8種狀況,那有沒有簡單的方法? 有的,實際上,無論哪一種狀況,咱們只須要關注畫面的x、y方向須要移動的距離便可。問題簡化爲求畫面在x、y軸上移動的距離:transAnimXtransAnimY 只要知道上述兩個值,將當前畫面位移進行位移,便可獲得動畫結束位置矩陣scaleEndAnimMatrix

    scaleEndAnimMatrix.set(mScaleTransMatrix);
     scaleEndAnimMatrix.postTranslate(transAnimX, transAnimY);
    複製代碼
  2. 如何計算畫面在屏幕內部須要移動到各屏幕邊緣的距離transAnimXtransAnimY? 要解決這個問題,須要知道屏幕位置播放畫面位置。 屏幕的位置很好辦,實際上就是畫面原始大小位置:RectF videoRectF = new RectF(0, 0, mTextureView.getWidth(), mTextureView.getHeight()); 當前縮放移動後畫面的位置呢? 它對應的矩陣變化是mScaleTransMatrix,那能不能根據這個矩陣推導出當前畫面的位置? 能夠的,咱們去找Matrix對外提供的接口,會發現有一個Matrix.mapRect(RectF)方法,這個方法就是用來測量矩形區域通過矩陣變化後,新的矩形區域所在位置。直接上代碼:

    if (mScale > 1.0F) { // 放大,檢測4邊是否有在屏幕內部,有的話自動吸附到屏幕邊緣
        RectF rectF = new RectF(0, 0, mTextureView.getWidth(), mTextureView.getHeight());
        mScaleTransMatrix.mapRect(rectF);
    
        float transAnimX = 0f;
        float transAnimY = 0f;
        scaleEndAnimMatrix.set(mScaleTransMatrix);
        if (rectF.left > videoRectF.left
                || rectF.right < videoRectF.right
                || rectF.top > videoRectF.top
                || rectF.bottom < videoRectF.bottom) { // 放大狀況下,有一邊縮放後在屏幕內部,自動吸附到屏幕邊緣
            if (rectF.left > videoRectF.left) { // 左移吸邊
                transAnimX = videoRectF.left - rectF.left;
            } else if (rectF.right < videoRectF.right) {  // 右移吸邊
                transAnimX = videoRectF.right - rectF.right;
            }
            // 注意這裏的處理方式:分別處理x軸位移和y軸位移便可所有覆蓋上述8種狀況
            if (rectF.top > videoRectF.top) {  // 上移吸邊
                transAnimY = videoRectF.top - rectF.top;
            } else if (rectF.bottom < videoRectF.bottom) { // 下移吸邊
                transAnimY = videoRectF.bottom - rectF.bottom;
            }
            // 計算移動到屏幕邊緣位置後的矩陣
            scaleEndAnimMatrix.postTranslate(transAnimX, transAnimY);
    }
    複製代碼

3. 項目完整代碼

github完整源碼

3.1 手勢縮放處理:VideoTouchScaleHandler

/** * 播放器畫面雙指手勢縮放處理: * <p> * 1. 雙指縮放 * 2. 雙指平移 * 3. 縮放結束後,若爲縮小畫面,居中動效 * 4. 縮放結束後,若爲放大畫面,自動吸附屏幕邊緣動效 * 5. 暫停播放下,實時更新縮放畫面 * * @author yinxuming * @date 2020/12/2 */
public class VideoTouchScaleHandler implements IVideoTouchHandler, ScaleGestureDetector.OnScaleGestureListener {
    private static final String TAG = "VideoTouchScaleHandler";


    private Context mContext;
    public FrameLayout mContainer;
    private boolean openScaleTouch = true; // 開啓縮放
    private boolean mIsScaleTouch;
    private Matrix mScaleTransMatrix; // 緩存了上次的矩陣值,因此須要計算每次變化量
    private float mStartCenterX, mStartCenterY, mLastCenterX, mLastCenterY, centerX, centerY;
    private float mStartSpan, mLastSpan, mCurrentSpan;
    private float mScale;
    private float[] mMatrixValue = new float[9];
    private float mMinScale = 0.1F, mMaxScale = 3F;
    private VideoScaleEndAnimator mScaleAnimator;

    IVideoTouchAdapter mTouchAdapter;
    TouchScaleResetView mScaleRestView;

    public VideoTouchScaleHandler(Context context, FrameLayout container, IVideoTouchAdapter videoTouchAdapter) {
        mContext = context;
        mContainer = container;
        mTouchAdapter = videoTouchAdapter;
        initView();
    }

    private void initView() {
        mScaleRestView = new TouchScaleResetView(mContext, mContainer) {
            @Override
            public void clickResetScale() {
                mScaleRestView.setVisibility(View.GONE);
                if (isScaled()) {
                    cancelScale();
                }
            }
        };
    }

    private Context getContext() {
        return mContext;
    }


    @Override
    public boolean onScaleBegin(ScaleGestureDetector detector) {

        TextureView mTextureView = mTouchAdapter.getTextureView();
        if (mTextureView != null) {
            mIsScaleTouch = true;
            if (mScaleTransMatrix == null) {
                mScaleTransMatrix = new Matrix(mTextureView.getMatrix());
                onScaleMatrixUpdate(mScaleTransMatrix);
            }
        }
        mStartCenterX = detector.getFocusX();
        mStartCenterY = detector.getFocusY();
        mStartSpan = detector.getCurrentSpan();

        mLastCenterX = mStartCenterX;
        mLastCenterY = mStartCenterY;
        mLastSpan = mStartSpan;
        return true;
    }

    private void updateMatrixToTexture(Matrix newMatrix) {
        TextureView mTextureView = mTouchAdapter.getTextureView();
        if (mTextureView != null) {
            mTextureView.setTransform(newMatrix);
        }
        onScaleMatrixUpdate(newMatrix);
    }

    @Override
    public boolean onScale(ScaleGestureDetector detector) {
        if (mIsScaleTouch && openScaleTouch) {
            mCurrentSpan = detector.getCurrentSpan();
            centerX = detector.getFocusX();
            centerY = detector.getFocusY();
            if (processOnScale(detector)) {
                mLastCenterX = centerX;
                mLastCenterY = centerY;
                mLastSpan = mCurrentSpan;
            }
        }

        return false;
    }

    private boolean processOnScale(ScaleGestureDetector detector) {
        float diffScale = mCurrentSpan / mLastSpan;
        if (mTouchAdapter.isFullScreen()) {
            if (mScaleTransMatrix != null) {
                postScale(mScaleTransMatrix, diffScale, mStartCenterX, mStartCenterY);
                mScaleTransMatrix.postTranslate(detector.getFocusX() - mLastCenterX,
                        detector.getFocusY() - mLastCenterY);
                onScaleMatrixUpdate(mScaleTransMatrix);
                TextureView mTextureView = mTouchAdapter.getTextureView();
                if (mTextureView != null) {
                    Matrix matrix = new Matrix(mTextureView.getMatrix());
                    matrix.set(mScaleTransMatrix);
                    mTextureView.setTransform(matrix);
                }
                int scaleRatio = (int) (mScale * 100);
                Toast.makeText(getContext(), "" + scaleRatio + "%", Toast.LENGTH_SHORT).show();
                return true;
            }
        }
        return false;
    }

    private void postScale(Matrix matrix, float scale, float x, float y) {
        matrix.getValues(mMatrixValue);
        float curScale = mMatrixValue[Matrix.MSCALE_X];
        if (scale < 1 && Math.abs(curScale - mMinScale) < 0.001F) {
            scale = 1;
        } else if (scale > 1 && Math.abs(curScale - mMaxScale) < 0.001F) {
            scale = 1;
        } else {
            curScale *= scale;
            if (scale < 1 && curScale < mMinScale) {
                curScale = mMinScale;
                scale = curScale / mMatrixValue[Matrix.MSCALE_X];
            } else if (scale > 1 && curScale > mMaxScale) {
                curScale = mMaxScale;
                scale = curScale / mMatrixValue[Matrix.MSCALE_X];
            }
            matrix.postScale(scale, scale, x, y);
        }
    }


    @Override
    public void onScaleEnd(ScaleGestureDetector detector) {
        if (mIsScaleTouch) { // 取消多手勢操做
            mIsScaleTouch = false;
            doScaleEndAnim();
        }
    }

    public void cancelScale() {
        TextureView mTextureView = mTouchAdapter.getTextureView();
        if (mScaleTransMatrix != null && mTextureView != null) {
            mIsScaleTouch = false;
            mScaleTransMatrix.reset();
            onScaleMatrixUpdate(mScaleTransMatrix);
            Matrix matrix = new Matrix(mTextureView.getMatrix());
            matrix.reset();
            mTextureView.setTransform(matrix);
        }
    }

    /** * 計算縮放結束後動畫位置:scaleEndAnimMatrix */
    private void doScaleEndAnim() {
        TextureView mTextureView = mTouchAdapter.getTextureView();
        if (mTextureView == null) {
            return;
        }
        Matrix scaleEndAnimMatrix = new Matrix();
        RectF videoRectF = new RectF(0, 0, mTextureView.getWidth(), mTextureView.getHeight());
        if (mScale > 0 && mScale <= 1.0f) { // 縮小居中
            scaleEndAnimMatrix.postScale(mScale, mScale, videoRectF.right / 2, videoRectF.bottom / 2);
            startTransToAnimEnd(mScaleTransMatrix, scaleEndAnimMatrix);
        } else if (mScale > 1.0F) { // 放大,檢測4邊是否有在屏幕內部,有的話自動吸附到屏幕邊緣
            RectF rectF = new RectF(0, 0, mTextureView.getWidth(), mTextureView.getHeight());
            // 測量通過縮放位移變換後的播放畫面位置
            mScaleTransMatrix.mapRect(rectF);
            float transAnimX = 0f;
            float transAnimY = 0f;
            scaleEndAnimMatrix.set(mScaleTransMatrix);
            if (rectF.left > videoRectF.left
                    || rectF.right < videoRectF.right
                    || rectF.top > videoRectF.top
                    || rectF.bottom < videoRectF.bottom) { // 放大狀況下,有一邊縮放後在屏幕內部,自動吸附到屏幕邊緣
                if (rectF.left > videoRectF.left) { // 左移吸邊
                    transAnimX = videoRectF.left - rectF.left;
                } else if (rectF.right < videoRectF.right) {  // 右移吸邊
                    transAnimX = videoRectF.right - rectF.right;
                }
                if (rectF.top > videoRectF.top) {  // 上移吸邊
                    transAnimY = videoRectF.top - rectF.top;
                } else if (rectF.bottom < videoRectF.bottom) { // 下移吸邊
                    transAnimY = videoRectF.bottom - rectF.bottom;
                }

                scaleEndAnimMatrix.postTranslate(transAnimX, transAnimY);
                startTransToAnimEnd(mScaleTransMatrix, scaleEndAnimMatrix);
            }
        }
    }

    private void startTransToAnimEnd(Matrix startMatrix, Matrix endMatrix) {
        LogUtil.d(TAG, "startTransToAnimEnd \nstart=" + startMatrix + "\nend=" + endMatrix);
        // 令 A = startMatrix;B = endMatrix
        // 方法1:直接將畫面更新爲結束矩陣位置B
// updateMatrixToView(endMatrix); //
        // 方法2:將畫面從現有位置A,移動到結束矩陣位置B,移動的距離T。B = T * A; 根據矩陣乘法的計算規則,反推出:T(x) = B(x) - A(x); T(y) = B(y) - A(y)
// float[] startArray = new float[9];
// float[] endArray = new float[9];
// startMatrix.getValues(startArray);
// endMatrix.getValues(endArray);
// float transX = endArray[Matrix.MTRANS_X] - startArray[Matrix.MTRANS_X];
// float transY = endArray[Matrix.MTRANS_Y] - startArray[Matrix.MTRANS_Y];
// startMatrix.postTranslate(transX, transY);
// LogUtil.d(TAG, "transToCenter1 \nstart=" + startMatrix + "\nend" + endMatrix);
// updateMatrixToView(startMatrix);

        // 方法3:在方法2基礎上,增長動畫移動效果
        if (mScaleAnimator != null) {
            mScaleAnimator.cancel();
            mScaleAnimator = null;
        }
        if (mScaleAnimator == null) {
            mScaleAnimator = new VideoScaleEndAnimator(startMatrix, endMatrix) {

                @Override
                protected void updateMatrixToView(Matrix transMatrix) {
                    updateMatrixToTexture(transMatrix);
                }
            };
            mScaleAnimator.start();
        }

        mScaleTransMatrix = endMatrix;
    }

    public void showScaleReset() {
        if (isScaled() && mTouchAdapter != null && mTouchAdapter.isFullScreen()) {
            if (mScaleRestView != null && mScaleRestView.getVisibility() != View.VISIBLE) {
                mScaleRestView.setVisibility(View.VISIBLE);
            }
        }
    }

    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
        // 縮放模式下,是否須要單手滾動
// if (isScaled(mScale) && mScaleTransMatrix != null) {
// TextureView mTextureView = mTouchAdapter.getTextureView();
// if (mTextureView != null) {
// postTranslate(mScaleTransMatrix, -distanceX, -distanceY);
// onScaleMatrixUpdate(mScaleTransMatrix);
// Matrix matrix = new Matrix(mTextureView.getMatrix());
// matrix.set(mScaleTransMatrix);
// mTextureView.setTransform(matrix);
// return true;
// }
// }
        return false;
    }



    private void onScaleMatrixUpdate(Matrix matrix) {
        matrix.getValues(mMatrixValue);
        mScale = mMatrixValue[Matrix.MSCALE_X];
        // 暫停下,實時更新縮放畫面
        if (!mTouchAdapter.isPlaying()) {
            TextureView mTextureView = mTouchAdapter.getTextureView();
            if (mTextureView != null) {
                mTextureView.invalidate();
            }
        }
    }

    /** * 是否處於已縮放 or 縮放中 * * @return */
    public boolean isInScaleStatus() {
        return isScaled(mScale) || mIsScaleTouch;
    }

    public boolean isScaled() {
        return isScaled(mScale);
    }

    private boolean isScaled(float scale) {
        return scale > 0 && scale <= 0.99F || scale >= 1.01F;
    }
}

複製代碼

3.2 動畫:VideoScaleEndAnimator

/** * 縮放動畫 * <p> * 在給定時間內從一個矩陣的變化逐漸動畫到另外一個矩陣的變化 */
public abstract class VideoScaleEndAnimator extends ValueAnimator implements ValueAnimator.AnimatorUpdateListener {
    private static final String TAG = "VideoScaleEndAnimator";

    /** * 圖片縮放動畫時間 */
    public static final int SCALE_ANIMATOR_DURATION = 300;

    Matrix mTransMatrix = new Matrix();
    float[] mTransSpan = new float[2];
    float mLastValue;

    /** * 構建一個縮放動畫 * <p> * 從一個矩陣變換到另一個矩陣 * * @param start 開始矩陣 * @param end 結束矩陣 */
    public VideoScaleEndAnimator(Matrix start, Matrix end) {
        this(start, end, SCALE_ANIMATOR_DURATION);
    }

    /** * 構建一個縮放動畫 * <p> * 從一個矩陣變換到另一個矩陣 * * @param start 開始矩陣 * @param end 結束矩陣 * @param duration 動畫時間 */
    public VideoScaleEndAnimator(Matrix start, Matrix end, long duration) {
        super();
        setFloatValues(0, 1f);
        setDuration(duration);
        addUpdateListener(this);

        float[] startValues = new float[9];
        float[] endValues = new float[9];
        start.getValues(startValues);
        end.getValues(endValues);
        mTransSpan[0] = endValues[Matrix.MTRANS_X] - startValues[Matrix.MTRANS_X];
        mTransSpan[1] = endValues[Matrix.MTRANS_Y] - startValues[Matrix.MTRANS_Y];
        mTransMatrix.set(start);
    }

    @Override
    public void onAnimationUpdate(ValueAnimator animation) {
        // 獲取動畫進度
        float value = (Float) animation.getAnimatedValue();
        // 計算相對於上次位置的偏移量
        float transX = mTransSpan[0] * (value - mLastValue);
        float transY = mTransSpan[1] * (value - mLastValue);
        mTransMatrix.postTranslate(transX, transY);
        updateMatrixToView(mTransMatrix);
        mLastValue = value;
    }

    protected abstract void updateMatrixToView(Matrix transMatrix);
}
複製代碼
相關文章
相關標籤/搜索