[toc]

The index

  1. Android video gesture zooming and rebound dynamic effect implementation (A) : mainly to achieve video double fingers: zooming, translation, rebound dynamic effect
  2. Android video rotation, zooming and rebound dynamic effect realization (TWO) : mainly to achieve video double fingers: rotation, zooming, translation, rebound dynamic effect

1. Functional requirements

  1. Double finger zoom video playback screen, support setting the minimum and maximum zoom range
  2. Double finger drag screen can move in any direction
  3. If it is zoomed out, it should be centered on the screen and animated
  4. If the screen is enlarged, the edge of the screen should be automatically attached to the edge of the screen
  5. Video can also be zoomed in and out when paused

2. Implementation principle

  1. Let’s scale and pan. throughView.getMatrix()Obtain the Matrix of the current playing picture, perform Matrix transformation: zoom, pan, change the position and size of the picture, and realize the zoom function of the playing picture.
  2. After scaling, animate the properties. The matrix transformation corresponding to the current picture ismScaleTransMatrixCalculates the bits that should be moved at the end of the animationscaleEndAnimMatrixTo animate properties frommScaleTransMatrixChange forscaleEndAnimMatrix.

2.1 How to detect gesture scaling?

  1. View.onTouchEvent. Listen for finger press (MotionEvent.ACTION_POINTER_DOWN), lift (MotionEvent.ACTION_POINTER_UP), move (MotionEvent.ACTION_MOVE)
  2. ScaleGestureDetector. Use gesture zoom detection directlyScaleGestureDetectorTo recognize gesture changes in View#onTouchEvent, passScaleGestureDetector.OnScaleGestureListenerGet a onScaleBegin – onScale – onScale… -onScaleEnd’s scaling callback, which handles the scaling logic of the response.

1. View.onTouchEvent key code

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;
}
Copy the code

2. ScaleGestureDetector

Use ScaleGestureDetector to identify gesture touch operations in onTouchEvent and get three callbacks onScaleBegin, onScale and onScaleEnd. In the callback, the VideoTouchScaleHandler is used to scale and pan the video.

  1. Add gesture touch layer GestureLayer and use ScaleGestureDetector to identify gestures

    /** * gesture processing layer */
    public final class GestureLayer implements IGestureLayer.GestureDetector.OnGestureListener.GestureDetector.OnDoubleTapListener {
        private static final String TAG = "GestureLayer";
    
        private Context mContext;
        private FrameLayout mContainer;
    
        /** Gesture detection */
        private GestureDetector mGestureDetector;
    
        /** Gesture zoom detection */
        private ScaleGestureDetector mScaleGestureDetector;
        /** gestures zoom listen */
        private VideoScaleGestureListener mScaleGestureListener;
        /** Gesture zooming */
        private VideoTouchScaleHandler mScaleHandler;
        private IVideoTouchAdapter mVideoTouchAdapter;
    
        public GestureLayer(Context context, IVideoTouchAdapter videoTouchAdapter) {
            mContext = context;
            mVideoTouchAdapter = videoTouchAdapter;
            initContainer();
            initTouchHandler();
        }
    
        private void initContainer(a) {
            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(a) {
            mGestureDetector = new GestureDetector(mContext, this);
            mGestureDetector.setOnDoubleTapListener(this);
    
            // Gesture zooming
            mScaleGestureListener = new VideoScaleGestureListener(this);
            mScaleGestureDetector = new ScaleGestureDetector(getContext(), mScaleGestureListener);
    
            // Zoom processing
            mScaleHandler = new VideoTouchScaleHandler(getContext(), mContainer, mVideoTouchAdapter);
            mScaleGestureListener.mScaleHandler = mScaleHandler;
    
        }
    
        @Override
        public void onLayerRelease(a) {
            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; }... }Copy the code
  2. ScaleGestureDetector OnScaleGestureListener gestures to zoom callback processing

    /** * Gestures to zoom in to play the screen */
    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); }}}Copy the code

2.2 Zooming and translation processing

  1. Double refers to zoomuseMatrix.postScale(float sx, float sy, float px, float py)There are several parameters, the first two specify the scale multiple on the x axis, the last two specify the scale center position.
    • How to calculate theZoom multiples? Scaling multiple = spacing between two fingers/spacing between two fingers last time:currentDiffScale = detector.getCurrentSpan() / mLastSpan
    • How to determine theZoom center? The zoom center is the central position point when the two fingers start to touch, i.eonScaleBeginWhen,scaleCenterX = detector.getFocusX(); scaleCenterY = detector.getFocusY();
    • postXXXandpreXXXThe difference between? PostXXX is the right multiplier, preXXX is the front multiplier. These two operations occur mainly becauseMatrix multiplication does not satisfy the commutative law, in the actual use process, fixed selection of a way. Let the original matrix M, the displacement transformation matrix T(x, y), then:
      M.postTranslate(tx, ty); // M' = T * M
      M.preTranslate(tx, ty); // equivalent M' = M * T
      Copy the code
  2. ‘Matrix. PostTranslate (float dx, float dy)

, dx and dy represent the distance to be moved relative to the current position of the Matrix, note that it must be relative to the current position of the Matrix, not relative to the initial position of the Matrix at onScaleBegin.

  • How do I determine the translation distance?
Java dx = Detector. GetFocusX () -mlastCenterx dy = Detector. GetFocusY () -mlastCentery ' 'Copy the code

2.3 Pause zooming

It is not processed by default. In the case of pausing the picture, the picture will not change after Matrix transformation and update to TextureView. To update the picture in real time, call textureview.invalidate ().

2.4 Dynamic effect after zooming

After zooming (onScaleEnd), in order to enhance the interactive experience, the screen needs to be adjusted according to the size and position of zooming, and the animation moves to the specified position. The designated position mainly has two kinds of center and adsorption screen edge. Animation movement, mainly using the property animation ValueAnimator.

1. Zoom out and center

After zooming, if the screen is in zoom mode, move the screen to the center of the screen.

  1. How to calculate theCenter position matrixChange the value? The transformation matrix is obtained at the end of the scaling displacementmScaleTransMatrix, which is also the starting value of the animation. Now we derive the end position matrix of the animationscaleEndAnimMatrix, should be centered on the screen if you want to use it directlymScaleTransMatrixTo obtain the end matrix of animation through transformation, it is necessary to shift xy by a certain distance, but this distance is not easy to calculate. So here we go in the other direction, and we know the current zoom timesmScale, the video TextureView occupies the region, then the central position matrix can be obtained by directly scaling the matrix at the center point of the regionscaleEndAnimMatrix
     RectF videoRectF = new RectF(0.0, mTextureView.getWidth(), mTextureView.getHeight());
     if (mScale > 0 && mScale <= 1.0 f) { // Zoom out to center
         scaleEndAnimMatrix.reset();
         scaleEndAnimMatrix.postScale(mScale, mScale, videoRectF.right / 2, videoRectF.bottom / 2);
     }
    Copy the code
  2. Property animation median, how to get the middle position transformation matrix?
    • Animation start matrix:mScaleTransMatrix;
    • Animation start matrix:scaleEndAnimMatrix;

    When themScaleTransMatrixMove the animation toscaleEndAnimMatrixIn position, the matrix in the middle is simply displaced by some distance in x and y. Take the X-axis as an example:

    1. Total displacement of x axis: totalTransX = MTRANS_X component value of scaleEndAnimMatrix matrix – MTRANS_X component value of mScaleTransMatrix matrix
    2. The x axis movement distance: transX = totalTransX change value = totalTransX * * the animation (animation) getAnimatedValue () – mLastValue);

2. Enlarge suction edge

After zooming, if the screen is zoomed in and the screen edge is inside the screen, it needs to be automatically attached to the screen edge.

  1. How to determine if there is a screen edge inside the screen? You need to consider four sides: left, top, right, bottom. If we want to consider the total number of cases of the picture inside the screen, it is tedious and complicated, for example, take left as an example: There are three cases:

    1. Left: Only the left edge is inside the screen, and the top and bottom edges are outside the screen. Just move the left edge of the screen to the left side of the screen
    2. Left + TOP: The left side and the top side are inside the screen, and you need to move the picture to the top left corner of the screen
    3. Left + bottom: Same as above, you need to move the image to the bottom left corner of the screen

    There are 8 cases. Is there an easy way to do that? Yes, in fact, in either case, we just need to focus on the distance we need to move in the X and y directions of the picture. The problem is simplified to find the moving distance of the picture on axis X and y: transAnimX and transAnimY. As long as the above two values are known, the current picture displacement is shifted, and the end position matrix scaleEndAnimMatrix can be obtained.

     scaleEndAnimMatrix.set(mScaleTransMatrix);
     scaleEndAnimMatrix.postTranslate(transAnimX, transAnimY);
    Copy the code
  2. TransAnimX, transAnimY how far does the screen need to move from inside the screen to the edge of each screen? To solve this problem, you need to know where the screen is, where the screen is playing. RectF videoRectF = new RectF(0, 0, mTexTuReview.getwidth (), mTexTureview.getheight ()); What is the current position of the screen after zooming? Its corresponding matrix change is mScaleTransMatrix, can we deduce the position of the current picture according to this matrix? Yes, we can find the external interface provided by Matrix, and we will find a matrix.mapRect (RectF) method, which is used to measure the position of the new rectangular area after the Matrix changes. Directly on the code:

    if (mScale > 1.0 F) { // Zoom in to check whether the 4 edges are inside the screen. If so, they are automatically attached to the edge of the screen
        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) { // When zoomed in, one side of the screen is zoomed in and automatically absorbs to the edge of the screen
            if (rectF.left > videoRectF.left) { // Move the edge to the left
                transAnimX = videoRectF.left - rectF.left;
            } else if (rectF.right < videoRectF.right) {  // Move the edge to the right
                transAnimX = videoRectF.right - rectF.right;
            }
            // Note the processing method here: the X-axis displacement and Y-axis displacement can cover all the above 8 cases respectively
            if (rectF.top > videoRectF.top) {  // Move the suction edge up
                transAnimY = videoRectF.top - rectF.top;
            } else if (rectF.bottom < videoRectF.bottom) { // Move the suction edge down
                transAnimY = videoRectF.bottom - rectF.bottom;
            }
            // Calculate the matrix after moving to the edge of the screen
            scaleEndAnimMatrix.postTranslate(transAnimX, transAnimY);
    }
    Copy the code

3. Complete project code

Github complete source code

3.1 Gesture scaling processing: VideoTouchScaleHandler

/** ** ** <p> * 1. Double finger zooming * 2. Double finger pan * 3. After zooming, if it is to reduce the picture, center motion effect * 4. After zooming, if you want to enlarge the picture, it will automatically absorb the screen edge dynamic effect * 5. Pause the playback and update the zooming picture * * in real time@author yinxuming
 * @date2020/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; // Enable scaling
    private boolean mIsScaleTouch;
    private Matrix mScaleTransMatrix; // The last matrix value is cached, so we need to calculate each change
    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.1 F, 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(a) {
        mScaleRestView = new TouchScaleResetView(mContext, mContainer) {
            @Override
            public void clickResetScale(a) {
                mScaleRestView.setVisibility(View.GONE);
                if(isScaled()) { cancelScale(); }}}; }private Context getContext(a) {
        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.001 F) {
            scale = 1;
        } else if (scale > 1 && Math.abs(curScale - mMaxScale) < 0.001 F) {
            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) { // Cancel multiple gestures
            mIsScaleTouch = false; doScaleEndAnim(); }}public void cancelScale(a) {
        TextureView mTextureView = mTouchAdapter.getTextureView();
        if(mScaleTransMatrix ! =null&& mTextureView ! =null) {
            mIsScaleTouch = false;
            mScaleTransMatrix.reset();
            onScaleMatrixUpdate(mScaleTransMatrix);
            Matrix matrix = newMatrix(mTextureView.getMatrix()); matrix.reset(); mTextureView.setTransform(matrix); }}/** * Calculates the animation position after scaling: scaleEndAnimMatrix */
    private void doScaleEndAnim(a) {
        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.0 f) { // Zoom out to center
            scaleEndAnimMatrix.postScale(mScale, mScale, videoRectF.right / 2, videoRectF.bottom / 2);
            startTransToAnimEnd(mScaleTransMatrix, scaleEndAnimMatrix);
        } else if (mScale > 1.0 F) { // Zoom in to check whether the 4 edges are inside the screen. If so, they are automatically attached to the edge of the screen
            RectF rectF = new RectF(0.0, mTextureView.getWidth(), mTextureView.getHeight());
            // Measure the position of the playback screen after scaling displacement transformation
            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) { // When zoomed in, one side of the screen is zoomed in and automatically absorbs to the edge of the screen
                if (rectF.left > videoRectF.left) { // Move the edge to the left
                    transAnimX = videoRectF.left - rectF.left;
                } else if (rectF.right < videoRectF.right) {  // Move the edge to the right
                    transAnimX = videoRectF.right - rectF.right;
                }
                if (rectF.top > videoRectF.top) {  // Move the suction edge up
                    transAnimY = videoRectF.top - rectF.top;
                } else if (rectF.bottom < videoRectF.bottom) { // Move the suction edge downtransAnimY = 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);
        // set A = startMatrix; B = endMatrix
        // Method 1: Directly update the screen to end matrix position B
// updateMatrixToView(endMatrix); //
        // Method 2: Move the screen from the existing position A to the end matrix position B, the move distance T. B = T * A; According to the calculation rules of matrix multiplication, 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);

        // Method 3: Based on method 2, add animation movement effect
        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(a) {
        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) {
        // Whether to scroll with one hand in zoom mode
// 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];
        // Pause to update the zoom screen in real time
        if(! mTouchAdapter.isPlaying()) { TextureView mTextureView = mTouchAdapter.getTextureView();if(mTextureView ! =null) { mTextureView.invalidate(); }}}/** * Whether to scale or scale **@return* /
    public boolean isInScaleStatus(a) {
        return isScaled(mScale) || mIsScaleTouch;
    }

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

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

3.2 Animation: VideoScaleEndAnimator


/** * Zoom animation * 

* gradually animate changes from one matrix to another matrix in a given time */

public abstract class VideoScaleEndAnimator extends ValueAnimator implements ValueAnimator.AnimatorUpdateListener { private static final String TAG = "VideoScaleEndAnimator"; /** * Picture zoom animation time */ public static final int SCALE_ANIMATOR_DURATION = 300; Matrix mTransMatrix = new Matrix(); float[] mTransSpan = new float[2]; float mLastValue; /** * Build a zoom animation * <p> * transform from one matrix to another matrix **@paramStart Start matrix *@paramEnd End matrix */ public VideoScaleEndAnimator(Matrix start, Matrix end) { this(start, end, SCALE_ANIMATOR_DURATION); } /** * Build a zoom animation * <p> * transform from one matrix to another matrix **@paramStart Start matrix *@paramEnd End matrix *@paramDuration Animation time */ public VideoScaleEndAnimator(Matrix start, Matrix end, long duration) { super(a); 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) { // Get the animation progress float value = (Float) animation.getAnimatedValue(); // Calculates the offset from the last position 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); } Copy the code