Android菜鳥練習第十一課 雙擊放大縮小也可以通過手勢改變大小的ZoomImageView

注意事項:本控件是以前同事做的,使用方法與ImageView相同,當給控件設置圖片是要使用src的方式而不是background的方式,否則會報空指針異常


public class ZoomImageView extends ImageView implements OnGlobalLayoutListener,
        OnScaleGestureListener, OnTouchListener {

    private boolean Ones = false;

    /**
     * 初始化時縮放的值
     */
    private float mInitscale;

    /**
     * 雙擊放大時縮放的值
     */
    private float mMidscale;
    /**
     * 縮放的最大值
     */
    private float mMaxscale;

    /**
     * 矩陣
     */
    private Matrix mScaleMatrix;

    /**
     * 捕獲用戶多點觸碰
     */
    private ScaleGestureDetector mScaleGestureDetector;

    // -------------自由移動-----------------

    /**
     * 記錄上一次多點觸碰的數量
     */
    private int mLastPointerCount;

    /**
     * 記錄上一次中心點X座標
     */
    private float mLastX;

    /**
     * 記錄上一次中心點y座標
     */
    private float mLasty;

    /**
     * 系統給標準值
     */
    private int mTouchSlop;

    /**
     * 是否可以移動
     */
    private boolean isCanDrag;

    /**
     * 是否檢查左邊界和右邊界
     */
    private boolean isCleckLeftAndRight;

    /**
     * 是否檢查上邊界和下邊界
     */
    private boolean isCleckTopAndBottom;

    // -------------雙擊放大與縮小--------
    private GestureDetector mGestureDetector;

    private boolean isAutoScale;


    public ZoomImageView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
        // 初始化矩陣
        mScaleMatrix = new Matrix();
        setScaleType(ScaleType.MATRIX);

        // 初始化多點觸碰
        mScaleGestureDetector = new ScaleGestureDetector(context, this);

        setOnTouchListener(this);
        // 雙擊初始化
        mGestureDetector = new GestureDetector(context,
                new GestureDetector.SimpleOnGestureListener() {
                    @Override
                    public boolean onDoubleTap(MotionEvent e) {

                        if (isAutoScale) {
                            return true;
                        }
                        float x = e.getX();
                        float y = e.getY();

                        if (getScale() < mMidscale) {
                            postDelayed(new AutoScaleRunnable(mMidscale, x, y), 16);
                            isAutoScale = true;
                        } else {
                            postDelayed(new AutoScaleRunnable(mInitscale, x, y), 16);
                            isAutoScale = true;
                        }
                        return true;
                    }
                });
    }

    /**
     * @author girl
     *         自動放大與縮小
     */
    private class AutoScaleRunnable implements Runnable {
        /**
         * 目標的縮放值
         */
        private float mTargetScale;
        /**
         * 目標縮放的x點座標
         */
        private float x;
        /**
         * 目標縮放的y點座標
         */
        private float y;

        /**
         * 放大的固定值
         */
        private final float BIGGER = 1.07f;
        /**
         * 縮小的固定值
         */
        private final float SMALL = 0.93f;

        /**
         * 存放臨時的變量
         */
        private float tmpScale;


        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;
            }
        }

        @Override
        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;
            }
        }


    }

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

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

    /*
     * (non-Javadoc) 當此view附加到窗體上時調用該方法()
     *
     * @see android.widget.ImageView#onAttachedToWindow()
     */
    @Override
    protected void onAttachedToWindow() {
        // TODO Auto-generated method stub
        super.onAttachedToWindow();
        getViewTreeObserver().addOnGlobalLayoutListener(this);
    }

    /*
     * (non-Javadoc)將視圖從窗體上分離的時候調用該方法
     *
     * @see android.widget.ImageView#onDetachedFromWindow()
     */
    @SuppressWarnings("deprecation")
    @Override
    protected void onDetachedFromWindow() {
        // TODO Auto-generated method stub
        super.onDetachedFromWindow();
        getViewTreeObserver().removeGlobalOnLayoutListener(this);
    }

    /**
     * 800 --- 1600 | | ---------- | | 1000 | |500 | | ---------- --- 500/1000
     * =0.5
     */
    /*
     * (non-Javadoc)獲取Imageview加載完成的圖片
    *
    * @see
    * android.view.ViewTreeObserver.OnGlobalLayoutListener#onGlobalLayout()
    */
    /*
     * (non-Javadoc)
    *
    * @see
    * android.view.ViewTreeObserver.OnGlobalLayoutListener#onGlobalLayout()
    */
    @Override
    public void onGlobalLayout() {

        if (!Ones) {
            // 獲得父類的寬和高
            int width = getWidth();
            int height = getHeight();

            // 獲得圖片以及圖片的寬和高
            Drawable drawable = getDrawable();
            int intrinsicWidth = drawable.getIntrinsicWidth();
            int intrinsicHeight = drawable.getIntrinsicHeight();

            // 計算縮放的比值

            float scale = 1.0f;

            // 圖片的寬大於控件的寬,高小於控件的高,縮放寬
            if (intrinsicWidth > width && intrinsicHeight < height) {
                scale = width * 1.0f / intrinsicWidth;
            }
            // 圖片的高大於控件的高,寬小於控件的寬,縮放高
            if (intrinsicHeight > height && intrinsicWidth < width) {
                scale = height * 1.0f / intrinsicHeight;
            }
            // 同大同小,則縮放寬高的最小值
            if ((intrinsicWidth < width && intrinsicHeight < height)
                    || (intrinsicWidth > width && intrinsicHeight > height)) {
                scale = Math.min(width * 1.0f / intrinsicWidth, height * 10f
                        / intrinsicHeight);
            }
            /**
             * 初始化縮放比例
             */
            mInitscale = scale;
            mMidscale = mInitscale * 2;
            mMaxscale = mInitscale * 4;

            // 將圖片移動到控件的中心
            int dx = getWidth() / 2 - intrinsicWidth / 2;
            int dy = getHeight() / 2 - intrinsicHeight / 2;

            // 初始化平移縮放
            mScaleMatrix.postTranslate(dx, dy);
            mScaleMatrix.postScale(mInitscale, mInitscale, width / 2,
                    height / 2);
            setImageMatrix(mScaleMatrix);

            Ones = true;
        }

    }

    /**
     * 獲取當前圖片的縮放值
     *
     * @return
     */
    public float getScale() {
        float[] values = new float[9];
        mScaleMatrix.getValues(values);
        return values[Matrix.MSCALE_X];

    }

    /*
     * (non-Javadoc)縮放中
     *
     * @see
     * android.view.ScaleGestureDetector.OnScaleGestureListener#onScale(android
     * .view.ScaleGestureDetector)
     */
    @Override
    public boolean onScale(ScaleGestureDetector detector) {
        // 獲取當前圖片的縮放值
        float scale = getScale();
        // 拿到縮放的值
        float scaleFactor = detector.getScaleFactor();

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

        if ((scale < mMaxscale && scaleFactor > 1.0f)
                || (scale > mInitscale && scaleFactor < 1.0f)) {

            if (scale * scaleFactor < mInitscale) {
                scaleFactor = mInitscale / scale;
            }

            if (scale * scaleFactor > mMaxscale) {
                scaleFactor = mMaxscale / scale;
            }
            // 以多點的中心點縮放
            mScaleMatrix.postScale(scaleFactor, scaleFactor,
                    detector.getFocusX(), detector.getFocusY());

            checkBorderAndCenterWhenScale();
            setImageMatrix(mScaleMatrix);
        }

        return false;
    }

    /**
     * 獲取放大縮小後的圖片的四點座標和位置
     *
     * @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 deltaX = 0;
        float deltaY = 0;

        int width = getWidth();
        int height = getHeight();
        // 縮放時進行邊界檢測,防止出現白邊
        if (rectF.width() >= width) {
            if (rectF.left > 0) {
                deltaX = -rectF.left;
            }

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

        if (rectF.height() >= height) {
            if (rectF.top > 0) {
                deltaY = -rectF.top;
            }
            if (rectF.bottom < height) {
                deltaY = height - rectF.bottom;
            }
        }

        // 如何高度或者寬度小於控件的高度或者寬度要居中顯示

        if (rectF.width() < width) {
            deltaX = width / 2f - rectF.right + rectF.width() / 2f;
        }

        if (rectF.height() < height) {
            deltaY = height / 2f - rectF.bottom + rectF.height() / 2f;
        }

        mScaleMatrix.postTranslate(deltaX, deltaY);
    }

    /*
     * (non-Javadoc)縮放開始
     *
     * @see
     * android.view.ScaleGestureDetector.OnScaleGestureListener#onScaleBegin
     * (android.view.ScaleGestureDetector)
     */
    @Override
    public boolean onScaleBegin(ScaleGestureDetector detector) {
        // TODO Auto-generated method stub
        return true;
    }

    /*
     * (non-Javadoc)縮放結束
     *
     * @see
     * android.view.ScaleGestureDetector.OnScaleGestureListener#onScaleEnd(android
     * .view.ScaleGestureDetector)
     */
    @Override
    public void onScaleEnd(ScaleGestureDetector detector) {
        // TODO Auto-generated method stub

    }

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

        // 將雙擊事件傳給GestureDetector處理
        if (mGestureDetector.onTouchEvent(event)) {
            return true;
        }

        // 將事件傳給ScaleGestureDetector處理
        mScaleGestureDetector.onTouchEvent(event);
        // 用於記錄多點觸碰的中心點
        float x = 0;
        float y = 0;
        // 多點觸碰的數量
        int pointerCount = event.getPointerCount();

        for (int i = 0; i < pointerCount; i++) {
            x += event.getX(i);
            y += event.getY(i);
        }

        x /= pointerCount;
        y /= pointerCount;

        if (mLastPointerCount != pointerCount) {
            isCanDrag = false;
            mLastX = x;
            mLasty = y;
        }
        mLastPointerCount = pointerCount;
        RectF rectF1 = getMatrixRectF();
        switch (event.getAction()) {

            case MotionEvent.ACTION_MOVE:

                float dx = x - mLastX;
                float dy = y - mLasty;

                if (!isCanDrag) {
                    isCanDrag = isMoveAction(dx, dy);
                }

                if (isCanDrag) {

                    RectF rectF = getMatrixRectF();

                    if (getDrawable() != null) {
                        isCleckLeftAndRight = isCleckTopAndBottom = true;

                        // 如何圖片寬度小於控件寬度,不允許橫向移動
                        if (rectF.width() < getWidth()) {
                            isCleckLeftAndRight = false;
                            dx = 0;
                        }
                        // 如果圖片高度小於控件高度,不允許上下移動
                        if (rectF.height() < getHeight()) {
                            isCleckTopAndBottom = false;
                            dy = 0;
                        }

                        mScaleMatrix.postTranslate(dx, dy);
                        checkBorderWhenTranslate();
                        setImageMatrix(mScaleMatrix);
                    }

                    if (rectF1.width() > getWidth() + 0.01 || rectF1.height() > getHeight() + 0.01) {
                        getParent().requestDisallowInterceptTouchEvent(true);
                    }
                }
                mLastX = x;
                mLasty = y;
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                mLastPointerCount = 0;
                break;
            default:
                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 && isCleckTopAndBottom) {
            deltaY = -rectF.top;
        }

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

        if (rectF.left > 0 && isCleckLeftAndRight) {
            deltaX = -rectF.left;
        }
        if (rectF.right < width && isCleckLeftAndRight) {
            deltaX = width - rectF.right;
        }

        mScaleMatrix.postTranslate(deltaX, deltaY);
    }

    private boolean isMoveAction(float dx, float dy) {
        // 算平方根
        return Math.sqrt(dx * dx + dy * dy) > mTouchSlop;
    }

}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章