(乾貨) Android實現ImageVIew多點觸控及雙擊縮放

支持多點觸控,放大自由移動,雙擊能夠放大縮小.直接上代碼:html

複製代碼

package com.cbt.view;

import android.content.Context;
import android.graphics.Matrix;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.Log;
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;


/**
 * Created by caobotao on 15/12/10.
 */
public class ZoomImageView extends ImageView implements OnGlobalLayoutListener, OnScaleGestureListener, OnTouchListener {
    private boolean mOnce;
    //初始化時所發的比例
    private float mInitScale;
    //雙擊後放大的比例
    private float mMidScale;
    //能夠放大的最大比例
    private float mMaxScale;

    private Matrix mMatrix;

    //經過ScaleGestureDetector能夠獲取到多點觸控的縮放比例
    private ScaleGestureDetector mScaleGestureDetector;

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

    //記錄上一次觸控點的數量
    private int mLastPointerCount;

    //上次多點觸控的中心點位置
    private float mLastX;
    private float mLastY;

    private int MTouchSlop;
    private boolean isCanDrag;

    private boolean isCheckLeftAndRight;
    private boolean isCheckTopAndBottom;

    //-------------雙擊放大與縮小----------
    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 defStyleAttr) {
        super(context, attrs, defStyleAttr);
        Log.i("ZoomImageView構造方法","ZoomImageView構造方法");
        mMatrix = new Matrix();
        super.setScaleType(ScaleType.MATRIX);
        mScaleGestureDetector = new ScaleGestureDetector(context,this);
        setOnTouchListener(this);
        MTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
        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) {
//                    mMatrix.postScale(mMidScale / getScale(),mMidScale / getScale(),x,y);
//                    setImageMatrix(mMatrix);
                    postDelayed(new AutoScaleRunnable(mMidScale,x,y),16);
                }
                else {
//                    mMatrix.postScale(mInitScale / getScale(),mInitScale / getScale(),x,y);
//                    setImageMatrix(mMatrix);
                    postDelayed(new AutoScaleRunnable(mInitScale,getWidth()/2,getHeight()/2),16);

                }
                isAutoScale = true;
                return true;
            }
        });
    }


    //當此view附加到窗體上時調用該方法
    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        //添加全局佈局監聽
        getViewTreeObserver().addOnGlobalLayoutListener(this);
    }

    //當此view從窗體上消除時調用該方法
    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        //移除全局佈局監聽
        getViewTreeObserver().removeOnGlobalLayoutListener(this);
    }

    /**
     *  獲取ImageView加載完成的圖片
     */
    @Override
    public void onGlobalLayout() {
        //因爲onGlobalLayout可能會被調用屢次,咱們使用一個標誌mOnce來判斷是否已經調用
        if (!mOnce) {
            //得到此View的寬和高
            float width = getWidth();
            float height = getHeight();
            Log.i("onGlobalLayout Width",width+"");
            Log.i("onGlobalLayout height",height+"");
            //獲得圖片及其寬高
            Drawable d = getDrawable();
            if (d == null) {
                return;
            }
            float dw = d.getIntrinsicWidth();
            float dh = d.getIntrinsicHeight();
            Log.i("onGlobalLayout dw",dw+"");
            Log.i("onGlobalLayout dh",dh+"");
            /**
             *  比較圖片的尺寸與此View的尺寸,若是圖片的尺寸比此View的尺寸大,
             *  則縮放,反之,則放大,以達到與此View尺寸一致
             */

            //縮放比例
            float scale = 1.0f;

            //若是圖片寬度比此View寬度大,且高度比此View小,則以寬度的比例縮小
            if (dw > width && dh < height) {
                scale = width / dw;
            }

            //若是圖片寬度比此View寬度小,且高度比此View大,則以高度的比例縮小
            if (dw < width && dh > height) {
                scale = height / dh;
            }

            //若是圖片寬度比此View寬度大,且高度比此View大,則以高度的比例與寬度的比例中大的一者縮小
            if ( (dw > width && dh > height) || (dw < width && dh < height) ) {
                scale = Math.max(width / dw,height/ dh);
            }

            //若是圖片寬度比此View寬度小,且高度比此View小,則以高度的比例與寬度的比例中小的一者放大
            if (dw < width && dh < height) {
                scale = Math.min(width / dw,height / dh);
            }

            //分別設置初始化時的比例,雙擊後的比例,能夠放大的最大比例
            mInitScale = scale;
            mMidScale = scale * 2;
            mMaxScale = scale * 4;
            //將圖片移動到此View的中心
            float dx = width / 2 - dw / 2;//須要移動的x方向的距離
            float dy = height / 2 - dh / 2;//須要移動的y方向的距離

            //設置平移
            mMatrix.postTranslate(dx,dy);
            //設置縮放
            mMatrix.postScale(mInitScale,mInitScale,width / 2,height / 2 );
            setImageMatrix(mMatrix);

            mOnce = true;
        }
    }

    //獲取當前圖片的縮放比例
    public float getScale(){
        float[] values = new float[9];
        mMatrix.getValues(values);
        return values[Matrix.MSCALE_X];
    }

    /**
     * 縮放區間:[initScale,maxScale]
     */
    @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;
            }
            //縮放
            mMatrix.postScale(scaleFactor,scaleFactor,detector.getFocusX(),detector.getFocusY());
            //在縮放的時候進行邊界以及位置的控制
            checkBorderAndCenterWhenScale();

            setImageMatrix(mMatrix);
        }

        return true;
    }

    //在縮放的時候進行邊界以及位置的控制
    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;
        }

        mMatrix.postTranslate(deltaX,deltaY);

    }

    //得到圖片縮放後的寬高,以及top,bottom,left,right
    private RectF getMatrixRectF(){
        Matrix matrix = mMatrix;
        RectF rectf = new RectF();
        Drawable d = getDrawable();
        if (d != null) {
            rectf.set(0,0,d.getIntrinsicWidth(),d.getIntrinsicHeight());
            matrix.mapRect(rectf);
        }
        return rectf;
    }

    @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;
        int pointerCount = event.getPointerCount();

        //累加x和y方向的距離
        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 rectF = getMatrixRectF();
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                /**
                 * 此View在ViewPager中使用時,圖片放大後自由移動的事件會與
                 * ViewPager的左右切換的事件發生衝突,致使圖片放大後若是左右
                 * 移動時不能自由移動圖片,而是使ViewPager切換圖片.這是因爲事
                 * 件分發時外層的優先級比內層的高,使用下列判斷能夠解決
                 */
                if (rectF.width() > getWidth() || rectF.height() > getHeight()) {
                    getParent().requestDisallowInterceptTouchEvent(true);
                }
                break;
            case MotionEvent.ACTION_MOVE:
                if (rectF.width() > getWidth() || rectF.height() > getHeight()) {
                    getParent().requestDisallowInterceptTouchEvent(true);
                }

                //偏移量
                float dx = x - mLastX;
                float dy = y - mLastY;

                if (!isCanDrag){
                    isCanDrag = isMoveAction(dx,dy);
                }
                if (isCanDrag) {
                    if (getDrawable() != null) {
                        isCheckLeftAndRight = true;
                        isCheckTopAndBottom = true;

                        //若是寬度小於控件的寬度,不容許橫向移動
                        if (rectF.width() < getWidth()) {
                            isCheckLeftAndRight = false;
                            dx = 0;
                        }

                        //若是高度小於控件的高度,不容許縱向移動
                        if (rectF.height() < getHeight()) {
                            isCheckTopAndBottom = false;
                            dy = 0;
                        }

                        mMatrix.postTranslate(dx,dy);
                        //當自由移動時進行邊界檢查,防止留白
                        checkBorderWhenTranslate();
                        setImageMatrix(mMatrix);
                    }
                }
                mLastX = x;
                mLastY = y;
                break;
            case MotionEvent.ACTION_CANCEL:
            case MotionEvent.ACTION_UP:
                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;
        }

        mMatrix.postTranslate(deltaX,deltaY);


    }

    //判斷是否足以觸發MOVE事件
    private boolean isMoveAction(float dx, float dy) {
        return Math.sqrt(dx * dx + dy * dy) > MTouchSlop;
    }


    //實現緩慢縮放
    private class AutoScaleRunnable implements Runnable{
        //縮放的目標比例
        private float mTargetScale;
        //縮放的中心點
        private float x;
        private float y;

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

        //臨時縮放比例
        private float tempScale;

        public AutoScaleRunnable(float mTargetScale,float x,float y) {
            this.mTargetScale = mTargetScale;
            this.x = x;
            this.y = y;
            if (getScale() < mTargetScale) {
                tempScale = BIGGER;
            }
            if (getScale() > mTargetScale) {
                tempScale = SMALLER;
            }
        }

        @Override
        public void run() {
            //進行縮放
            mMatrix.postScale(tempScale,tempScale,x,y);
            checkBorderAndCenterWhenScale();
            setImageMatrix(mMatrix);

            float currentScale = getScale();
            //若是能夠放大或者縮小
            if ((tempScale > 1.0f && currentScale < mTargetScale) || (tempScale < 1.0f && currentScale > mTargetScale) ){
                postDelayed(this,16);
            }
            //設置爲目標縮放比例
            else {
                float scale = mTargetScale / currentScale;
                mMatrix.postScale(scale,scale,x,y);
                checkBorderAndCenterWhenScale();
                setImageMatrix(mMatrix);
                isAutoScale = false;
            }
        }
    }
}

複製代碼

 

做者:caobotaoandroid

出處:http://www.cnblogs.com/caobotao/p/5041737.htmlide

本文版權歸做者和博客園共有,歡迎轉載,但未經做者贊成必須保留此段聲明,且在文章頁面明顯位置給出原文連接,不然保留追究法律責任的權利佈局

相關文章
相關標籤/搜索