com.guodong.sun.guodong.widget.ZoomImageView.java Source code

Java tutorial

Introduction

Here is the source code for com.guodong.sun.guodong.widget.ZoomImageView.java

Source

/*
 * Copyright (C) 2017 guodongAndroid
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * Last modified 2017-05-04 15:07:09
 *
 * GitHub:   https://github.com/guodongAndroid
 * Website:  http://www.sunxiaoduo.com
 * Email:    sun33919135@gmail.com
 * QQ:       33919135
 */

package com.guodong.sun.guodong.widget;

import android.animation.Animator;
import android.animation.PropertyValuesHolder;
import android.animation.ValueAnimator;
import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.RectF;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.support.annotation.Nullable;
import android.support.v4.view.ViewPager;
import android.util.AttributeSet;
import android.util.Log;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewTreeObserver;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.widget.ImageView;

/**
 * 2d?ImageView
 * ?:ScaleType==CENTER_CROPImageView??ScaleType=
 * FIT_CENTERImageView?? (?)
 */
public class ZoomImageView extends ImageView implements ScaleGestureDetector.OnScaleGestureListener,
        ViewTreeObserver.OnGlobalLayoutListener, View.OnTouchListener {
    private static final int STATE_NORMAL = 0;
    private static final int STATE_TRANSFORM_IN = 1;
    private static final int STATE_TRANSFORM_OUT = 2;
    private int mOriginalWidth;
    private int mOriginalHeight;
    private int mOriginalLocationX;
    private int mOriginalLocationY;
    private int mState = STATE_NORMAL;
    private Matrix mSmoothMatrix;
    private Bitmap mBitmap;
    private boolean mTransformStart = false;
    private Transfrom mTransfrom;
    private final int mBgColor = 0xFF000000;
    private int mBgAlpha = 0;
    private Paint mPaint;

    private boolean mOnce = true;
    /**
     * ?
     */
    private float mMinScale;
    /**
     * ?
     */
    private float mMidScale;
    /**
     * 
     */
    private float mMaxScale;

    private Matrix mScaleMatrix;
    /**
     * ?
     */
    private ScaleGestureDetector mScaleGestureDetector = null;
    /**
     * ?
     */
    private int mLastPointerCount;
    private float mLastX;
    private float mLastY;

    private int mTouchSlop;
    private boolean isCanDrag;

    private boolean isCheckLeftAndRight = true;
    private boolean isCheckTopAndBottom = true;

    /**
     * ??
     */
    private GestureDetector mGestureDetector;

    private boolean isAutoScale;

    public ZoomImageView(Context context) {
        this(context, null);
    }

    public ZoomImageView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public ZoomImageView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init(context);
    }

    private void init(Context context) {
        mSmoothMatrix = new Matrix();
        mPaint = new Paint();
        mPaint.setColor(mBgColor);
        mPaint.setStyle(Style.FILL);
        //      setBackgroundColor(mBgColor);

        mScaleMatrix = new Matrix();
        setScaleType(ScaleType.MATRIX);

        mScaleGestureDetector = new ScaleGestureDetector(context, this);

        setOnTouchListener(this);

        mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();

        mGestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() {
            public boolean onDoubleTap(MotionEvent e) {

                if (isAutoScale == true) {
                    return true;
                }

                float X = e.getX();
                float Y = e.getY();

                if (getScale() < mMidScale) {

                    postDelayed(new AutoScaleRunnable(mMidScale, X, Y), 16);
                    isAutoScale = true;

                } else if ((getScale() >= mMidScale) && (getScale() < mMaxScale)) {

                    postDelayed(new AutoScaleRunnable(mMaxScale, X, Y), 16);
                    isAutoScale = true;

                } else {

                    postDelayed(new AutoScaleRunnable(mMinScale, X, Y), 5);
                    isAutoScale = true;

                }
                return true;
            }

            @Override
            public boolean onSingleTapConfirmed(MotionEvent e) {
                if (mClickListener != null)
                    mClickListener.onClick(ZoomImageView.this);
                return super.onSingleTapUp(e);
            }

            @Override
            public void onLongPress(MotionEvent e) {
                if (mLongClickListener != null)
                    mLongClickListener.onLongClick(ZoomImageView.this);
            }
        });
    }

    /**
     * ?
     *
     * @author Sun
     */
    private class AutoScaleRunnable implements Runnable {

        /**
         * 
         */
        private float mTargetScale;
        // 
        private float X;
        private float Y;

        private final float BIGGER = 1.07f;
        private final float SMALL = 0.93f;

        private float tmpScale;

        /**
         * ???
         *
         * @param
         */
        public AutoScaleRunnable(float mTargetScale, float x, float y) {

            this.mTargetScale = mTargetScale;
            this.X = x;
            this.Y = y;

            if (getScale() < mTargetScale) {
                tmpScale = BIGGER;
            }

            if (getScale() > mTargetScale) {
                tmpScale = SMALL;
            }
        }

        public void run() {

            // 
            mScaleMatrix.postScale(tmpScale, tmpScale, X, Y);
            checkBorderAndCenterWhenScale();
            setImageMatrix(mScaleMatrix);

            float currentScale = getScale();
            // ?
            if (((tmpScale > 1.0f) && (currentScale < mTargetScale))
                    || ((tmpScale < 1.0f) && (currentScale > mTargetScale))) {

                postDelayed(this, 16);
            } else {
                // 
                float scale = mTargetScale / currentScale;
                mScaleMatrix.postScale(scale, scale, X, Y);
                checkBorderAndCenterWhenScale();
                setImageMatrix(mScaleMatrix);
                isAutoScale = false;
            }
        }
    }

    /**
     * onGlobalLayout?
     */
    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        getViewTreeObserver().addOnGlobalLayoutListener(this);
    }

    /**
     * onGlobalLayout?
     */
    @SuppressWarnings("deprecation")
    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        // getViewTreeObserver().removeOnGlobalLayoutListener(this);
        getViewTreeObserver().removeGlobalOnLayoutListener(this);
    }

    /**
     * ?ImageView?
     */
    @Override
    public void onGlobalLayout() {

        if (mOnce) {

            // ?
            Drawable d = getDrawable();
            if (d == null) {
                return;
            }

            // 
            int width = getWidth();
            int height = getHeight();

            int dWidth = d.getIntrinsicWidth();
            int dHeight = d.getIntrinsicHeight();

            float scale = 1.0f;

            // ??
            if (dWidth > width && dHeight <= height) {
                scale = width * 1.0f / dWidth;
            }

            // ??
            if (dHeight > height && dWidth <= width) {
                scale = height * 1.0f / dHeight;
            }

            // ?
            // ??
            if ((dWidth > width && dHeight > height) || (dWidth < width && dHeight < height)) {

                scale = Math.min(width * 1.0f / dWidth, height * 1.0f / dHeight);
            }

            // ?
            mMinScale = scale;
            mMidScale = mMinScale * 2f;
            mMaxScale = mMinScale * 4f;

            // ?X?Y?
            int dX = (width - dWidth) / 2;
            int dY = (height - dHeight) / 2;
            // 
            mScaleMatrix.postTranslate(dX, dY);
            mScaleMatrix.postScale(mMinScale, mMinScale, width / 2, height / 2);
            setImageMatrix(mScaleMatrix);

            mOnce = false;
        }
    }

    /**
     * ?
     *
     * @return
     */
    public float getScale() {

        float[] values = new float[9];
        mScaleMatrix.getValues(values);
        return values[Matrix.MSCALE_X];
    }

    @Override
    public boolean onScale(ScaleGestureDetector detector) {

        // mMinScale~mMaxScale

        float scale = getScale();
        float scaleFactor = detector.getScaleFactor();

        if (getDrawable() == null)
            return true;

        // 
        if ((scale < mMaxScale && scaleFactor > 1.0f) || (scale > mMinScale && scaleFactor < 1.0f)) {

            if (scale * scaleFactor < mMinScale) {

                scaleFactor = mMinScale / scale;
            }

            if (scale * scaleFactor > mMaxScale) {

                scale = mMaxScale / scale;
            }
            // 
            mScaleMatrix.postScale(scaleFactor, scaleFactor, detector.getFocusX(), detector.getFocusY());

            checkBorderAndCenterWhenScale();

            setImageMatrix(mScaleMatrix);
        }

        return true;
    }

    /**
     * ???l,r,t,b
     *
     * @return
     */
    private RectF getMatrixRectF() {

        Matrix matrix = mScaleMatrix;
        RectF rectF = new RectF();
        Drawable d = getDrawable();

        if (d != null) {

            rectF.set(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());
            matrix.mapRect(rectF);
        }
        return rectF;
    }

    /**
     * ?
     */
    private void checkBorderAndCenterWhenScale() {

        RectF rectF = getMatrixRectF();

        float delatX = 0;
        float delatY = 0;

        int width = getWidth();
        int height = getHeight();

        // 
        if (rectF.width() >= width) {

            if (rectF.left > 0) {
                delatX = -rectF.left;
            }

            if (rectF.right < width) {
                delatX = width - rectF.right;
            }
        }

        if (rectF.height() >= height) {

            if (rectF.top > 0) {
                delatY = -rectF.top;
            }

            if (rectF.bottom < height) {
                delatY = height - rectF.bottom;
            }
        }

        // ?
        if (rectF.width() < width) {
            delatX = width * 0.5f - rectF.right + rectF.width() * 0.5f;
        }

        if (rectF.height() < height) {
            delatY = height * 0.5f - rectF.bottom + rectF.height() * 0.5f;
        }

        mScaleMatrix.postTranslate(delatX, delatY);

    }

    @Override
    public boolean onScaleBegin(ScaleGestureDetector detector) {

        return true;
    }

    @Override
    public void onScaleEnd(ScaleGestureDetector detector) {

    }

    @Override
    public boolean onTouch(View v, MotionEvent event) {

        if (mGestureDetector.onTouchEvent(event))
            return true;

        mScaleGestureDetector.onTouchEvent(event);

        float x = 0;
        float y = 0;
        // ?
        final int mPointerCount = event.getPointerCount();
        for (int i = 0; i < mPointerCount; i++) {

            x += event.getX(i);
            y += event.getY(i);
        }

        x = x / mPointerCount;
        y = y / mPointerCount;

        /**
         * ????mLasX , mLastY
         */
        if (mLastPointerCount != mPointerCount) {
            isCanDrag = false;
            mLastX = x;
            mLastY = y;
        }

        mLastPointerCount = mPointerCount;

        RectF rectF = getMatrixRectF();

        switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:

            if (rectF.width() > getWidth() + 0.01 || rectF.height() > getHeight() + 0.01) {
                if (getParent() instanceof ViewPager)
                    getParent().requestDisallowInterceptTouchEvent(true);
            }
            break;
        case MotionEvent.ACTION_MOVE:

            if (rectF.width() > getWidth() + 0.01 || rectF.height() > getHeight() + 0.01) {
                if (getParent() instanceof ViewPager)
                    getParent().requestDisallowInterceptTouchEvent(true);
            }

            float dX = x - mLastX;
            float dY = y - mLastY;

            if (!isCanDrag) {
                isCanDrag = isMoveAction(dX, dY);
            }

            if (isCanDrag) {
                if (getDrawable() != null) {

                    isCheckLeftAndRight = isCheckTopAndBottom = true;

                    // ????
                    if (rectF.width() < getWidth()) {
                        dX = 0;
                        isCheckLeftAndRight = false;
                    }
                    // ????
                    if (rectF.height() < getHeight()) {
                        dY = 0;
                        isCheckTopAndBottom = false;
                    }

                    mScaleMatrix.postTranslate(dX, dY);

                    checkBorderWhenTranslate();

                    setImageMatrix(mScaleMatrix);

                }
            }
            mLastX = x;
            mLastY = y;
            break;

        case MotionEvent.ACTION_UP:
        case MotionEvent.ACTION_CANCEL:
            mLastPointerCount = 0;
            break;
        }
        return true;
    }

    /**
     * 
     */
    private void checkBorderWhenTranslate() {

        RectF rectF = getMatrixRectF();

        float deltaX = 0;
        float deltaY = 0;

        int width = getWidth();
        int height = getHeight();

        if (rectF.top > 0 && isCheckTopAndBottom) {
            deltaY = -rectF.top;
        }

        if (rectF.bottom < height && isCheckTopAndBottom) {
            deltaY = height - rectF.bottom;
        }

        if (rectF.left > 0 && isCheckLeftAndRight) {
            deltaX = -rectF.left;
        }

        if (rectF.right < width && isCheckLeftAndRight) {
            deltaX = width - rectF.right;
        }

        mScaleMatrix.postTranslate(deltaX, deltaY);
        //      setImageMatrix(mScaleMatrix);

    }

    /**
     * ?move
     *
     * @param dX
     * @param dY
     * @return
     */
    private boolean isMoveAction(float dX, float dY) {
        return Math.sqrt((dX * dX) + (dY * dY)) >= mTouchSlop;
    }

    public void setOriginalInfo(int width, int height, int locationX, int locationY) {
        mOriginalWidth = width;
        mOriginalHeight = height;
        mOriginalLocationX = locationX;
        mOriginalLocationY = locationY;
        // ???????MATCH_PARENT???,??????mOriginalLocationXmOriginalLocationY
        mOriginalLocationY = mOriginalLocationY - getStatusBarHeight(getContext());
    }

    /**
     * ???
     *
     * @return
     */
    private int getStatusBarHeight(Context context) {
        Class<?> c = null;
        Object obj = null;
        java.lang.reflect.Field field = null;
        int x = 0;
        int statusBarHeight = 0;
        try {
            c = Class.forName("com.android.internal.R$dimen");
            obj = c.newInstance();
            field = c.getField("status_bar_height");
            x = Integer.parseInt(field.get(obj).toString());
            statusBarHeight = context.getResources().getDimensionPixelSize(x);
            return statusBarHeight;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return statusBarHeight;
    }

    /**
     *  ??setOriginalInfo
     */
    public void transformIn() {
        mState = STATE_TRANSFORM_IN;
        mTransformStart = true;
        invalidate();
    }

    /**
     *  ??setOriginalInfo
     */
    public void transformOut() {
        mState = STATE_TRANSFORM_OUT;
        mTransformStart = true;
        invalidate();
    }

    private class Transfrom {
        float startScale;// 
        float endScale;// ?
        float scale;// ValueAnimator?
        LocationSizeF startRect;// 
        LocationSizeF endRect;// ?
        LocationSizeF rect;// ValueAnimator?

        void initStartIn() {
            scale = startScale;
            try {
                rect = (LocationSizeF) startRect.clone();
            } catch (CloneNotSupportedException e) {
                e.printStackTrace();
            }
        }

        void initStartOut() {
            scale = endScale;
            try {
                rect = (LocationSizeF) endRect.clone();
            } catch (CloneNotSupportedException e) {
                e.printStackTrace();
            }
        }

    }

    /**
     * ????
     */
    private void initTransform() {
        if (getDrawable() == null) {
            return;
        }
        if (mBitmap == null || mBitmap.isRecycled()) {
            mBitmap = ((BitmapDrawable) getDrawable()).getBitmap();
        }
        //mTransfrom?????
        if (mTransfrom != null) {
            return;
        }
        if (getWidth() == 0 || getHeight() == 0) {
            return;
        }
        mTransfrom = new Transfrom();

        /** ? */
        /* ??CENTR_CROP???1??1 */
        float xSScale = mOriginalWidth / ((float) mBitmap.getWidth());
        float ySScale = mOriginalHeight / ((float) mBitmap.getHeight());
        float startScale = xSScale > ySScale ? xSScale : ySScale;
        mTransfrom.startScale = startScale;
        /* ???FIT_CENTER???1??1? */
        float xEScale = getWidth() / ((float) mBitmap.getWidth());
        float yEScale = getHeight() / ((float) mBitmap.getHeight());
        float endScale = xEScale < yEScale ? xEScale : yEScale;
        mTransfrom.endScale = endScale;

        /**
         * ?Canvas Clip????
         * ?CENTER_CROP
         * FIT_CENTERLocationSizeF
         * ???Canvas??Rect.
         */
        /*  */
        mTransfrom.startRect = new LocationSizeF();
        mTransfrom.startRect.left = mOriginalLocationX;
        mTransfrom.startRect.top = mOriginalLocationY;
        mTransfrom.startRect.width = mOriginalWidth;
        mTransfrom.startRect.height = mOriginalHeight;
        /* ? */
        mTransfrom.endRect = new LocationSizeF();
        float bitmapEndWidth = mBitmap.getWidth() * mTransfrom.endScale;// 
        float bitmapEndHeight = mBitmap.getHeight() * mTransfrom.endScale;// 
        mTransfrom.endRect.left = (getWidth() - bitmapEndWidth) / 2;
        mTransfrom.endRect.top = (getHeight() - bitmapEndHeight) / 2;
        mTransfrom.endRect.width = bitmapEndWidth;
        mTransfrom.endRect.height = bitmapEndHeight;

        mTransfrom.rect = new LocationSizeF();
    }

    private class LocationSizeF implements Cloneable {
        float left;
        float top;
        float width;
        float height;

        @Override
        public String toString() {
            return "[left:" + left + " top:" + top + " width:" + width + " height:" + height + "]";
        }

        @Override
        public Object clone() throws CloneNotSupportedException {
            // TODO Auto-generated method stub
            return super.clone();
        }

    }

    /* ?CENTER_CROP Matrix?? */
    private void getCenterCropMatrix() {
        if (getDrawable() == null) {
            return;
        }
        if (mBitmap == null || mBitmap.isRecycled()) {
            mBitmap = ((BitmapDrawable) getDrawable()).getBitmap();
        }
        /* ?CENTER_CROP */
        float xScale = mOriginalWidth / ((float) mBitmap.getWidth());
        float yScale = mOriginalHeight / ((float) mBitmap.getHeight());
        float scale = xScale > yScale ? xScale : yScale;
        mSmoothMatrix.reset();
        mSmoothMatrix.setScale(scale, scale);
        mSmoothMatrix.postTranslate(-(scale * mBitmap.getWidth() / 2 - mOriginalWidth / 2),
                -(scale * mBitmap.getHeight() / 2 - mOriginalHeight / 2));
    }

    private void getBmpMatrix() {
        if (getDrawable() == null) {
            return;
        }
        if (mTransfrom == null) {
            return;
        }
        if (mBitmap == null || mBitmap.isRecycled()) {
            mBitmap = ((BitmapDrawable) getDrawable()).getBitmap();
        }
        /* ?CENTER_CROP */
        mSmoothMatrix.setScale(mTransfrom.scale, mTransfrom.scale);
        mSmoothMatrix.postTranslate(-(mTransfrom.scale * mBitmap.getWidth() / 2 - mTransfrom.rect.width / 2),
                -(mTransfrom.scale * mBitmap.getHeight() / 2 - mTransfrom.rect.height / 2));
    }

    @Override
    protected void onDraw(Canvas canvas) {
        if (getDrawable() == null) {
            return; // couldn't resolve the URI
        }

        if (mState == STATE_TRANSFORM_IN || mState == STATE_TRANSFORM_OUT) {
            if (mTransformStart) {
                initTransform();
            }
            if (mTransfrom == null) {
                super.onDraw(canvas);
                return;
            }

            if (mTransformStart) {
                if (mState == STATE_TRANSFORM_IN) {
                    mTransfrom.initStartIn();
                } else {
                    mTransfrom.initStartOut();
                }
            }

            if (mTransformStart) {
                Log.d("Dean", "mTransfrom.startScale:" + mTransfrom.startScale);
                Log.d("Dean", "mTransfrom.startScale:" + mTransfrom.endScale);
                Log.d("Dean", "mTransfrom.scale:" + mTransfrom.scale);
                Log.d("Dean", "mTransfrom.startRect:" + mTransfrom.startRect.toString());
                Log.d("Dean", "mTransfrom.endRect:" + mTransfrom.endRect.toString());
                Log.d("Dean", "mTransfrom.rect:" + mTransfrom.rect.toString());
            }

            mPaint.setAlpha(mBgAlpha);
            canvas.drawPaint(mPaint);

            int saveCount = canvas.getSaveCount();
            canvas.save();
            // ?Matrix
            getBmpMatrix();
            canvas.translate(mTransfrom.rect.left, mTransfrom.rect.top);
            canvas.clipRect(0, 0, mTransfrom.rect.width, mTransfrom.rect.height);
            canvas.concat(mSmoothMatrix);
            getDrawable().draw(canvas);
            canvas.restoreToCount(saveCount);
            if (mTransformStart) {
                mTransformStart = false;
                startTransform(mState);
            }
        } else {
            //Transform In???Activity??
            mPaint.setAlpha(255);
            canvas.drawPaint(mPaint);
            super.onDraw(canvas);
        }
    }

    private void startTransform(final int state) {
        if (mTransfrom == null) {
            return;
        }
        ValueAnimator valueAnimator = new ValueAnimator();
        valueAnimator.setDuration(300);
        valueAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
        if (state == STATE_TRANSFORM_IN) {
            PropertyValuesHolder scaleHolder = PropertyValuesHolder.ofFloat("scale", mTransfrom.startScale,
                    mTransfrom.endScale);
            PropertyValuesHolder leftHolder = PropertyValuesHolder.ofFloat("left", mTransfrom.startRect.left,
                    mTransfrom.endRect.left);
            PropertyValuesHolder topHolder = PropertyValuesHolder.ofFloat("top", mTransfrom.startRect.top,
                    mTransfrom.endRect.top);
            PropertyValuesHolder widthHolder = PropertyValuesHolder.ofFloat("width", mTransfrom.startRect.width,
                    mTransfrom.endRect.width);
            PropertyValuesHolder heightHolder = PropertyValuesHolder.ofFloat("height", mTransfrom.startRect.height,
                    mTransfrom.endRect.height);
            PropertyValuesHolder alphaHolder = PropertyValuesHolder.ofInt("alpha", 0, 255);
            valueAnimator.setValues(scaleHolder, leftHolder, topHolder, widthHolder, heightHolder, alphaHolder);
        } else {
            PropertyValuesHolder scaleHolder = PropertyValuesHolder.ofFloat("scale", mTransfrom.endScale,
                    mTransfrom.startScale);
            PropertyValuesHolder leftHolder = PropertyValuesHolder.ofFloat("left", mTransfrom.endRect.left,
                    mTransfrom.startRect.left);
            PropertyValuesHolder topHolder = PropertyValuesHolder.ofFloat("top", mTransfrom.endRect.top,
                    mTransfrom.startRect.top);
            PropertyValuesHolder widthHolder = PropertyValuesHolder.ofFloat("width", mTransfrom.endRect.width,
                    mTransfrom.startRect.width);
            PropertyValuesHolder heightHolder = PropertyValuesHolder.ofFloat("height", mTransfrom.endRect.height,
                    mTransfrom.startRect.height);
            PropertyValuesHolder alphaHolder = PropertyValuesHolder.ofInt("alpha", 255, 0);
            valueAnimator.setValues(scaleHolder, leftHolder, topHolder, widthHolder, heightHolder, alphaHolder);
        }

        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public synchronized void onAnimationUpdate(ValueAnimator animation) {
                mTransfrom.scale = (Float) animation.getAnimatedValue("scale");
                mTransfrom.rect.left = (Float) animation.getAnimatedValue("left");
                mTransfrom.rect.top = (Float) animation.getAnimatedValue("top");
                mTransfrom.rect.width = (Float) animation.getAnimatedValue("width");
                mTransfrom.rect.height = (Float) animation.getAnimatedValue("height");
                mBgAlpha = (Integer) animation.getAnimatedValue("alpha");
                invalidate();
                ((Activity) getContext()).getWindow().getDecorView().invalidate();
            }
        });
        valueAnimator.addListener(new ValueAnimator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animation) {

            }

            @Override
            public void onAnimationRepeat(Animator animation) {

            }

            @Override
            public void onAnimationEnd(Animator animation) {
                /*
                 * ???center_cropout??center_crop?
                 *  ???out???Normal???bug
                 */
                // TODO ???
                if (state == STATE_TRANSFORM_IN) {
                    mState = STATE_NORMAL;
                }
                if (mTransformListener != null) {
                    mTransformListener.onTransformComplete(state);
                }
            }

            @Override
            public void onAnimationCancel(Animator animation) {

            }
        });
        valueAnimator.start();
    }

    public void setOnTransformListener(TransformListener listener) {
        mTransformListener = listener;
    }

    private TransformListener mTransformListener;

    public static interface TransformListener {
        /**
         * @param mode STATE_TRANSFORM_IN 1 ,STATE_TRANSFORM_OUT 2
         */
        void onTransformComplete(int mode);// mode 1
    }

    public interface OnClickListener {
        void onClick(View v);
    }

    private OnClickListener mClickListener;

    public void setOnClickListener(@Nullable OnClickListener l) {
        this.mClickListener = l;
    }

    public interface OnLongClickListener {
        void onLongClick(View v);
    }

    private OnLongClickListener mLongClickListener;

    public void setOnLongClickListener(@Nullable OnLongClickListener l) {
        mLongClickListener = l;
    }

}