1. 程式人生 > >自定義ImageView: 實現自由縮放 ,自由移動縮放後的圖片 .雙擊放大與縮小圖片 相容ViewPager

自定義ImageView: 實現自由縮放 ,自由移動縮放後的圖片 .雙擊放大與縮小圖片 相容ViewPager

直接擼程式碼, 複製就能用

package com.zhf.baselibrary.view;

import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Matrix;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.support.v4.view.ViewPager;
import android.util.AttributeSet;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.ScaleGestureDetector.OnScaleGestureListener;
import android.view.View;
import android.view.View.OnTouchListener;
import android.view.ViewConfiguration;
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
import android.widget.ImageView;

/**
 * 自定義ImageView:
 * 1. 初步實現多點觸控、自由縮放 ,處理圖片自由縮放出現的間隙  預設
 * 2. 多點觸控之自由移動縮放後的圖片
 * 3. 雙擊放大與縮小圖片
 * 4. 相容ViewPager
 */

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

    private Context context;    //上下文
    private boolean mOnce = false;//是否執行了一次

    /**
     * 初始縮放的比例
     */
    private float initScale;
    /**
     * 縮放比例
     */
    private float midScale;
    /**
     * 可放大的最大比例
     */
    private float maxScale;
    /**
     * 可放大的最大比例
     */
    private float minScale;
    /**
     * 縮放矩陣
     */
    private Matrix scaleMatrix;

    /**
     * 縮放的手勢監控類
     */
    private ScaleGestureDetector mScaleGestureDetector;


    /**
     * 上一次移動的手指個數,也可以說是多點個數
     */
    private int mLastPoint;
    /**
     * 上次的中心點的x位置
     */
    private float mLastX;
    /**
     * 上一次中心點的y位置
     */
    private float mLastY;
    /**
     * 一個臨界值,即是否觸發移動的臨界值
     */
    private float mScaleSlop;
    /**
     * 是否可移動
     */
    private boolean isCanDrag = false;


    /**
     * 監測各種手勢事件,例如雙擊
     */
    private GestureDetector mGestureDetector;
    /**
     * 是否正在執行雙擊縮放
     */
    private boolean isAutoScale;
///////////////////////////////////////////////////////////////////////////////

    /**
     * 自定義是否 自由縮放
     */
    private boolean autoZoom = false;
    /**
     * 最大放大倍數
     */
    private int maxRatio = 4;
    /**
     * 最小縮小倍數
     */
    private int minRatio = 2;


    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);
        this.context = context;
        //用來操作 image的  矩陣
        scaleMatrix = new Matrix();
        setScaleType(ScaleType.MATRIX);
        //手勢回撥
        mScaleGestureDetector = new ScaleGestureDetector(context, this);
        //觸控回撥
        setOnTouchListener(this);
        //獲得系統給定的觸發移動效果的臨界值
        mScaleSlop = ViewConfiguration.get(context).getScaledTouchSlop();
    }

    /**
     * 設定是否支援雙擊放大
     */
    public void setDoubleZoom(boolean doubleZoom) {
        //雙擊放大2倍 縮小
        if (doubleZoom) {
            mGestureDetector = new GestureDetector(context, new MySimpleOnGestureListener());
        } else {
            mGestureDetector = null;
        }
    }

    /**
     * 設定是否支援手勢縮放
     */
    public void setAutoZoom(boolean autoZoom) {
        this.autoZoom = autoZoom;
    }

    /**
     * 該方法在view與window繫結時被呼叫,且只會被呼叫一次,其在view的onDraw方法之前呼叫
     */
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        //註冊監聽器
        getViewTreeObserver().addOnGlobalLayoutListener(this);
    }

    /**
     * 該方法在view被銷燬時被呼叫
     */
    @SuppressLint("NewApi")
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        //取消監聽器
        getViewTreeObserver().removeOnGlobalLayoutListener(this);
    }

    /**
     * 當一個view的佈局載入完成或者佈局發生改變時,OnGlobalLayoutListener會監聽到,呼叫該方法
     * 因此該方法可能會被多次呼叫,需要在合適的地方註冊和取消監聽器
     * <p>
     * 圖片居中全屏View顯示
     */
    public void onGlobalLayout() {
        if (!mOnce) {
            //獲得當前view的Drawable
            Drawable d = getDrawable();

            if (d == null) {
                return;
            }

            //獲得Drawable的寬和高
            int dw = d.getIntrinsicWidth();
            int dh = d.getIntrinsicHeight();

            //獲取當前view的寬和高
            int width = getWidth();
            int height = getHeight();

            //縮放的比例,scale可能是縮小的比例也可能是放大的比例,看它的值是大於1還是小於1
            float scale = 1.0f;

            //如果僅僅是圖片寬度比view寬度大,則應該將圖片按寬度縮小
            if (dw > width && dh < height) {
                scale = width * 1.0f / dw;
            }
            //如果圖片和高度都比view的大,則應該按最小的比例縮小圖片
            if (dw > width && dh > height) {
                scale = Math.min(width * 1.0f / dw, height * 1.0f / dh);
            }
            //如果圖片寬度和高度都比view的要小,則應該按最小的比例放大圖片
            if (dw < width && dh < height) {
                scale = Math.min(width * 1.0f / dw, height * 1.0f / dh);
            }
            //如果僅僅是高度比view的大,則按照高度縮小圖片即可
            if (dw < width && dh > height) {
                scale = height * 1.0f / dh;
            }

            //初始化縮放的比例
            initScale = scale;
            midScale = initScale * 2;
            maxScale = initScale * maxRatio;
            minScale = initScale / minRatio;

            //移動圖片到達view的中心
            int dx = width / 2 - dw / 2;
            int dy = height / 2 - dh / 2;
            scaleMatrix.postTranslate(dx, dy);

            //縮放圖片
            scaleMatrix.postScale(initScale, initScale, width / 2, height / 2);

            setImageMatrix(scaleMatrix);
            mOnce = true;
        }
    }

    /**
     * 獲取當前已經縮放的比例
     *
     * @return 因為x方向和y方向比例相同,所以只返回x方向的縮放比例即可
     */
    private float getDrawableScale() {

        float[] values = new float[9];
        scaleMatrix.getValues(values);

        return values[Matrix.MSCALE_X];

    }

    /**
     * 縮放手勢開始時呼叫該方法
     */
    public boolean onScaleBegin(ScaleGestureDetector detector) {
        //返回為true,則縮放手勢事件往下進行,否則到此為止,即不會執行onScale和onScaleEnd方法
        return autoZoom;
    }

    /**
     * 縮放手勢完成後呼叫該方法
     */
    public void onScaleEnd(ScaleGestureDetector detector) {

    }

    /**
     * 縮放手勢進行時呼叫該方法
     * <p>
     * 縮放範圍:initScale~maxScale
     */
    public boolean onScale(ScaleGestureDetector detector) {

        if (getDrawable() == null) {
            return true;//如果沒有圖片,下面的程式碼沒有必要執行
        }

        //獲取當前已經縮放的比例
        float scale = getDrawableScale();
        //獲取當前縮放因子,指的是手指縮放手勢  使用者預縮放的值
        float scaleFactor = detector.getScaleFactor();

        //如果當前縮放比例比最大比例小,且縮放因子大於1,說明想放大,這是被允許的,因為還還可以再放大。
        //如果當前縮放比例比最小比例大,且縮放因子小於1,說明想縮小,這也是被允許的。
        if ((scale < maxScale
                && scaleFactor > 1.0f)
                || (scale > minScale
                && scaleFactor < 1.0f)) {

            //如果縮小的範圍比允許的最小範圍還要小,就重置縮放因子為當前的狀態的因子
            if (scale * scaleFactor < minScale && scaleFactor < 1.0f) {
                scaleFactor = minScale / scale;
            }

            //如果縮小的範圍比允許的最小範圍還要小,就重置縮放因子為當前的狀態的因子
            if (scale * scaleFactor > maxScale && scaleFactor > 1.0f) {
                scaleFactor = maxScale / scale;
            }
            //scaleMatrix.postScale(1.0f, 1.0f, getWidth() / 2, getHeight() / 2);
            scaleMatrix.postScale(scaleFactor, scaleFactor, detector.getFocusX(), detector.getFocusY());

            checkBoderAndCenter();//處理縮放後圖片邊界與螢幕有間隙或者不居中的問題

            setImageMatrix(scaleMatrix);//千萬不要忘記設定這個,我總是忘記
        }
        return true;
    }

    /**
     * 實現雙擊放大或者縮小圖片。用到的知識點就是GestureDetector,用它來監測雙擊事件。
     * 至於雙擊後怎麼縮放圖片,相信在前面幾篇文章中,你都已經很熟悉了。
     * 但是難點是,我們要求雙擊後緩慢的放大或者縮小,而不是一下子就放大到或者縮小到目標值。
     * 這裡就要結合線程來處理了。其實處理的邏輯也很簡單:
     * 比如說放大,我們每隔一段時間,就對圖片進行放大一次,然後看看是不是達到要求的放大比例了,
     * 如果達到了就終止,否則繼續放大,直到達到要求為止。
     */
    private class MySimpleOnGestureListener extends GestureDetector.SimpleOnGestureListener {
        public boolean onDoubleTap(MotionEvent e) {
            //如果正在執行雙擊縮放,直接跳過
            if (isAutoScale) {
                return true;
            }
            float x = e.getX();
            float y = e.getY();
            //獲得當前的縮放比例
            float scale = getDrawableScale();
            //如果比midScale小,一律放大,否則一律縮小為initScale
            if (scale < midScale) {
                //  scaleMatrix.postScale(midScale/scale,midScale/scale, x, y);
                //  setImageMatrix(scaleMatrix);
                postDelayed(new AutoScaleRunnable(midScale, x, y), 16);
                isAutoScale = true;
            } else {
                //  scaleMatrix.postScale(initScale/scale,initScale/scale, x, y);
                //  setImageMatrix(scaleMatrix);
                postDelayed(new AutoScaleRunnable(initScale, x, y), 16);
                isAutoScale = true;
            }
            return true;
        }
    }

    /**
     * 將 雙擊縮放使用梯度
     *
     * @author fuly1314
     */
    private class AutoScaleRunnable implements Runnable {

        private float targetScale;//縮放的目標值
        private float x;
        private float y;//縮放的中心點
        private float tempScale;
        private float BIGGER = 1.07F;
        private float SMALL = 0.93F;//縮放的梯度

        public AutoScaleRunnable(float targetScale, float x, float y) {
            super();
            this.targetScale = targetScale;
            this.x = x;
            this.y = y;

            if (getDrawableScale() < targetScale) {
                tempScale = BIGGER;
            }
            if (getDrawableScale() > targetScale) {
                tempScale = SMALL;
            }
        }

        public void run() {
            scaleMatrix.postScale(tempScale, tempScale, x, y);
            checkBoderAndCenter();
            setImageMatrix(scaleMatrix);
            float scale = getDrawableScale();
            if ((scale < targetScale && tempScale > 1.0f) || (scale > targetScale && tempScale < 1.0f)) {
                postDelayed(this, 16);
            } else {
                scaleMatrix.postScale(targetScale / scale, targetScale / scale, x, y);
                checkBoderAndCenter();
                setImageMatrix(scaleMatrix);
                isAutoScale = false;
            }
        }
    }

    /**
     * 處理縮放後圖片邊界與螢幕有間隙或者不居中的問題
     */
    private void checkBoderAndCenter() {
        RectF rectf = getDrawableRectF();
        int width = getWidth();
        int height = getHeight();

        float delaX = 0;
        float delaY = 0;

        if (rectf.width() >= width) {
            if (rectf.left > 0) {
                delaX = -rectf.left;
            }

            if (rectf.right < width) {
                delaX = width - rectf.right;
            }
        }

        if (rectf.height() >= height) {
            if (rectf.top > 0) {
                delaY = -rectf.top;
            }
            if (rectf.bottom < height) {
                delaY = height - rectf.bottom;
            }
        }

        if (rectf.width() < width) {
            delaX = width / 2 - rectf.right + rectf.width() / 2;
        }

        if (rectf.height() < height) {
            delaY = height / 2 - rectf.bottom + rectf.height() / 2;
        }

        scaleMatrix.postTranslate(delaX, delaY);
    }

    /**
     * 獲取圖片根據矩陣變換後的四個角的座標,即left,top,right,bottom
     *
     * @return
     */
    private RectF getDrawableRectF() {
        Matrix matrix = scaleMatrix;
        RectF rectf = new RectF();
        Drawable d = getDrawable();
        if (d != null) {

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


    /**
     * 監聽觸控事件
     */
    public boolean onTouch(View v, MotionEvent event) {

        if (mGestureDetector != null && mGestureDetector.onTouchEvent(event)) {
            return true;
        }

        if (mScaleGestureDetector != null) {
            //將觸控事件傳遞給手勢縮放這個類
            mScaleGestureDetector.onTouchEvent(event);
        }

        //獲得多點個數,也叫螢幕上手指的個數
        int pointCount = event.getPointerCount();

        float x = 0;
        float y = 0;//中心點的x和y

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

        //求出中心點的位置
        x /= pointCount;
        y /= pointCount;

        //如果手指的數量發生了改變,則不移動
        if (mLastPoint != pointCount) {
            isCanDrag = false;
            mLastX = x;
            mLastY = y;

        }
        mLastPoint = pointCount;
        RectF rectf = getDrawableRectF();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                if (rectf.width() > getWidth() + 0.01 || rectf.height() > getHeight() + 0.01) {
                    //請求父類不要攔截ACTION_DOWN事件
                    if (getParent() instanceof ViewPager)
                        this.getParent().requestDisallowInterceptTouchEvent(true);
                }
                break;
            case MotionEvent.ACTION_MOVE:
                if (rectf.width() > getWidth() + 0.01 || rectf.height() > getHeight() + 0.01) {
                    //請求父類不要攔截ACTION_MOVE事件
                    if (getParent() instanceof ViewPager)
                        this.getParent().requestDisallowInterceptTouchEvent(true);
                }
                //求出移動的距離
                float dx = x - mLastX;
                float dy = y - mLastY;

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

                if (isCanDrag) {
                    //如果圖片能正常顯示,就不需要移動了
                    if (rectf.width() <= getWidth()) {
                        dx = 0;
                    }
                    if (rectf.height() <= getHeight()) {
                        dy = 0;
                    }

                    //開始移動
                    scaleMatrix.postTranslate(dx, dy);
                    //處理移動後圖片邊界與螢幕有間隙或者不居中的問題
                    checkBoderAndCenterWhenMove();
                    setImageMatrix(scaleMatrix);
                }

                mLastX = x;
                mLastY = y;


                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                mLastPoint = 0;
                break;

        }

        return true;
    }

    /**
     * 處理移動後圖片邊界與螢幕有間隙或者不居中的問題
     * 這跟我們前面寫的程式碼很像
     */
    private void checkBoderAndCenterWhenMove() {

        RectF rectf = getDrawableRectF();

        float delaX = 0;
        float delaY = 0;
        int width = getWidth();
        int height = getHeight();

        if (rectf.width() > width && rectf.left > 0) {
            delaX = -rectf.left;
        }
        if (rectf.width() > width && rectf.right < width) {
            delaX = width - rectf.right;
        }
        if (rectf.height() > height && rectf.top > 0) {
            delaY = -rectf.top;
        }
        if (rectf.height() > height && rectf.bottom < height) {
            delaY = height - rectf.bottom;
        }
        scaleMatrix.postTranslate(delaX, delaY);
    }

    /**
     * 判斷是否觸發移動效果
     *
     * @param dx
     * @param dy
     * @return
     */
    private boolean isCanDrag(float dx, float dy) {
        return Math.sqrt(dx * dx + dy * dy) > mScaleSlop;
    }
}

我們不生產程式碼,我們是網際網路的搬運工,感謝作者:https://www.cnblogs.com/fuly550871915/