自定義視圖之————安卓圖庫縮放拖拽的完整實現

加了大部分註釋,看註釋應該能夠明白基本的思路。歡迎大神留言拍磚,此文適合小白觀看。java

package com.example.imagedeal;

import android.content.Context;
import android.graphics.Matrix;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.GestureDetector;
import android.view.GestureDetector.SimpleOnGestureListener;
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;
/*
 * @OnGlobalLayoutListener 手勢縮放監聽
 * @OnScaleGestureListener 得到控件寬高
 * @OnTouchListener 觸摸事件監聽器
 */
public class CustomImage extends ImageView implements OnGlobalLayoutListener,OnScaleGestureListener,OnTouchListener{
    private Matrix matrix;     
    private ScaleGestureDetector scaleGestureDetector;
    private int touchSlop;      //判斷滑動距離的最小值,大於此值,才認爲滑動
    private GestureDetector gestureDetector;
    public CustomImage(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        matrix = new Matrix();
        scaleGestureDetector = new ScaleGestureDetector(context, this);
        setOnTouchListener(this);
        touchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
        //@Focous實現此功能的第六步,雙擊縮小放大
        gestureDetector = new GestureDetector(context, new SimpleOnGestureListener(){
            @Override
            public boolean onDoubleTap(MotionEvent e) {
                float x = e.getX();
                float y = e.getY();
                float currScale = getCurrScale();
                //縮小
                if (currScale>=doubleScale) {
                    //matrix.postScale(defScale/currScale, defScale/currScale, x, y);
                    post(new SlowScale(x, y, defScale));
                }else{ 
                    post(new SlowScale(x, y, doubleScale));
                    //matrix.postScale(doubleScale/currScale, doubleScale/currScale, x, y);
                }
                checkBorderByScale();
                setImageMatrix(matrix);
                return true;
            }
        });
    }
    /*
     * 實現梯度縮放
     */
    class SlowScale implements Runnable{
        private float x,y,targetScale;
        private float tempScale;//臨時縮放比例
        private static final float BIGGER = 1.05f;
        private static final float SMALLER = 0.95f;
        public SlowScale(float x, float y, float targetScale) {
            this.x = x;
            this.y = y;
            this.targetScale = targetScale;
            float scale = getCurrScale();
            if (scale>=targetScale) {
                tempScale = SMALLER;
            }else{
                tempScale = BIGGER;
            }
        }

        @Override
        public void run() {
            matrix.postScale(tempScale, tempScale, x, y);
            checkBorderByScale();
            setImageMatrix(matrix);
            if ((getCurrScale()>targetScale&&tempScale<1.0f)||
                    getCurrScale()<targetScale&&tempScale>1.0f) {
                postDelayed(this, 20);
            }else{
                matrix.postScale(targetScale/getCurrScale(), targetScale/getCurrScale(), x, y);
                checkBorderByScale();
                setImageMatrix(matrix);
            }
        }
        
    }
    public CustomImage(Context context, AttributeSet attrs) {
        this(context, attrs,0);
    }
    public CustomImage(Context context) {
        this(context,null);
    }
    /*@Focus 實現此功能的第一步
     *@來源 實現View類就能夠重寫此方法
     *@onAttachedToWindow() 在第一次調用onDraw方法以前調用,在此用來註冊觀察者類(getViewTreeObserver())
     *@getViewTreeObserver() 時間的觀察者,用來註冊addOnGlobalLayoutListener監聽器。實現對手勢的監聽
     */
    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        getViewTreeObserver().addOnGlobalLayoutListener(this);//詳見http://blog.csdn.net/x1617044578/article/details/39668667
    }
    /*@Focus 實現此功能的第一步
     *@來源 實現View類就能夠重寫此方法
     *@onDetachedFromWindow() 在銷燬View以後調用,作收尾工.在此用來取消註冊觀察者類(getViewTreeObserver())
     */
    @SuppressWarnings("deprecation")
    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        getViewTreeObserver().removeGlobalOnLayoutListener(this);
    }
    /*
     * @isInited 至關於鎖,讓程序對控件之類的寬高只進行一次計算
     * @defScale 默認的縮放比率
     * @doubleScale 雙擊縮放比率
     * @maxScale 最大縮放比率
     * @Focus 實現此功能的第二步,測量控件和圖片的寬高,而且計算縮放率
     */
    private boolean isInited;     
    private int width,height,dw,dh;//with,height:控件的寬高   dw,dh圖片的寬高
    private float defScale,doubleScale,maxScale;
    @Override
    public void onGlobalLayout() {
        if (!isInited) {
            width = getWidth();
            height = getHeight();
            
            Drawable drawable = getDrawable();
            if (drawable==null) {
                return;
            }
            dw = drawable.getIntrinsicWidth();
            dh = drawable.getIntrinsicHeight();
            
            float scaleX = width*1.0f/dw;   //X軸上的縮放量,即寬的縮放量
            float scaleY = height*1.0f/dh;   //Y軸上的縮放量,即高的縮放量
            float scale  = 1.0f;             //初始的縮放量,1.0,即不縮放。
            if ((dw>width&&dh>height)||(dw<width&&dh<height)) {  //圖片比屏幕大或者圖片比屏幕小都進入
                scale = Math.min(scaleX, scaleY);   //縮小時,取小是由於無論哪一種狀況,說明最小的那個值對應的圖片的屬性最大,讓它顯示,屬性小的那一方絕對會正確顯示。放大時本身思考!
            }else if(dw>width||dh>height){      //圖片的一個屬性比屏幕大都會進入此
                scale = (scaleX>scaleY)?scaleY:scaleX;   //縮放時取最小值保證dw與dh較大的那一方能夠鋪滿屏幕,較小的留白顯示,上面的也是一個效果。
            }
            defScale = scale;
            doubleScale = defScale*2;
            maxScale = defScale*4;
            float dx = (width-dw)/2;   //平移時X軸的中心點
            float dy = (height-dh)/2;  //平移時Y軸的中心點
            matrix.postTranslate(dx, dy);  //先平移
            matrix.postScale(defScale, defScale, width/2, height/2); //再縮放
            setImageMatrix(matrix);
            isInited = true;
        }
    }
    /*
     * 得到當前的縮放比率
     */
    private float getCurrScale(){
        float[] values = new float[9];
        matrix.getValues(values );
        return values[Matrix.MSCALE_X];
    }
    /*
     *@onScale() 縮放時時時調用此方法
     *@URL http://blog.csdn.net/lmj623565791/article/details/39474553 對Matrix和縮放的應用介紹的還好
     */
    @Override
    public boolean onScale(ScaleGestureDetector detector) {
        float scaleFactor = detector.getScaleFactor();  //縮放前的縮放比率
        float currScale = getCurrScale();   //當前的縮放比率
        float scale = currScale*scaleFactor;
        //當前的縮放值比最大縮放值小  想放大     當前的縮放值大於初始縮放值  想縮小
        if ((currScale<maxScale&&scaleFactor>1.0f)||
                (currScale>defScale&&scaleFactor<1.0f)) {
            if (scale>maxScale) {
                scaleFactor = maxScale/currScale; //放大時,控制縮放比率,不超過最大值
            }
            if (scale<defScale) {
                scaleFactor = defScale/currScale;//縮小時,控制縮放比率,不超過最小值
            }
            matrix.postScale(scaleFactor, scaleFactor, detector.getFocusX(), detector.getFocusY()); //在detector.getFocusX()(X軸上的觸摸焦點)點與在detector.getFocus()(軸上的觸摸焦點)上進行縮放
            checkBorderByScale(); //檢查縮放時不能留白
            setImageMatrix(matrix);
        }
        return true;
    }
    private RectF getMatrixRectF(){
        RectF rectF = new RectF(0, 0, dw, dh);
        matrix.mapRect(rectF);
        return rectF;
    }
    /*
     * @Focus 實現此功能的第三步,保證縮放時不越界,實現辦法是平移圖片至控件中心點
     * @width 控件的寬
     * @height 控件的高
     */
    private void checkBorderByScale() {
        RectF rectF = getMatrixRectF();  //將當前圖像抽象爲矩形,得到長寬
        float dx=0 ,dy = 0;     //X,Y軸上的平移量
        //防止出現白邊 縮小
        if (rectF.width()>=width) {
            //判斷是左邊和右邊哪邊越界
            if (rectF.left>0) {
                dx = -rectF.left;
            }
            if (rectF.right<width) {
                dx = width - rectF.right;
            }
        }
        //判斷是上下哪邊越界
        if (rectF.height()>=height) {
            if (rectF.top>0) {
                dy = -rectF.top;
            }
            if (rectF.bottom<height) {
                dy = height-rectF.bottom;
            }
        }
        //圖片居中
        if (rectF.width()<width) {
            dx = width/2f -rectF.right+rectF.width()/2f;
        }
        if (rectF.height()<height) {
            dy = height/2f-rectF.bottom+rectF.height()/2f;
        }
        matrix.postTranslate(dx, dy);
        
    }

    @Override
    public boolean onScaleBegin(ScaleGestureDetector detector) {
        // 返回true
        return true;
    }

    @Override
    public void onScaleEnd(ScaleGestureDetector detector) {
        
    }
    /*
     * @Focus 實現此功能的第四步,拖動
     */
    private int prePointerCount; //觸摸時前一次的觸摸點的數
    private float preX,preY; //觸摸時前一次的X點和Y點的座標
    private boolean isDrag;  //是否拖拽
    private boolean checkLeftAndRight,checkTopAndBottom;
    @Override
    public boolean onTouch(View v, MotionEvent event) {
        if (gestureDetector.onTouchEvent(event)) {
            return true; //閱讀http://blog.csdn.net/tianfeng701/article/details/7556366
        }
        scaleGestureDetector.onTouchEvent(event);
        int pointerCount = event.getPointerCount();  //得到觸摸點的數
        //記錄中心點的座標
        float x =0,y = 0;
        for(int i = 0 ; i<pointerCount;i++){
            x+= event.getX(i);
            y+= event.getY(i);
        }
        //計算觸摸時的中心點
        x/=pointerCount;
        y/=pointerCount;
        if (prePointerCount!=pointerCount) {
            preX = x;
            preY = y;
        }
        prePointerCount = pointerCount;
    
        switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            isDrag = false;
            break;
        case MotionEvent.ACTION_MOVE:
            RectF rectF = getMatrixRectF();
            float dx = x - preX;
            float dy = y - preY;
            if (!isDrag) {
                checkLeftAndRight = checkTopAndBottom = true;
                isDrag = checkIsDrag(dx,dy); //判斷是否要開始拖拽
            }
            if (isDrag) {
                if (rectF.width()<width) { //判斷X方向有沒有超過屏幕
                    checkLeftAndRight = false;
                    dx = 0;
                }
                if (rectF.height()<height) {//判斷Y方向有沒有超過屏幕
                    checkTopAndBottom = false;
                    dy = 0 ;
                }
                matrix.postTranslate(dx, dy);
                checkBorderByTransLate(); //檢查邊框
                setImageMatrix(matrix);
            }
            preX = x;
            preY = y;
            break;
        case MotionEvent.ACTION_UP:
            prePointerCount = 0; //鬆手時觸摸點歸零
            break;
        }
        return true;
    }

    private void checkBorderByTransLate() {
        RectF rectF = getMatrixRectF();
        float dx=0,dy=0;
        if (checkLeftAndRight&&rectF.left>0) {
            dx = -rectF.left;
        }
        if (checkLeftAndRight&&rectF.right<width) {
            dx = width - rectF.right;
        }
        if (checkTopAndBottom&&rectF.top>0) {
            dy = -rectF.top;
        }
        
        if (checkTopAndBottom&&rectF.bottom<height) {
            dy = height-rectF.bottom;
        }
        matrix.postTranslate(dx, dy);
    }

    private boolean checkIsDrag(float dx, float dy) {
        return Math.sqrt(dx*dx+dy*dy)>touchSlop;
    }

}
相關文章
相關標籤/搜索