码迷,mamicode.com
首页 > 移动开发 > 详细

自定义视图之————安卓图库缩放拖拽的完整实现

时间:2015-08-04 23:26:34      阅读:426      评论:0      收藏:0      [点我收藏+]

标签:

加了大部分注释,看注释应该可以明白基本的思路。欢迎大神留言拍砖,此文适合小白观看。

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 是否要触摸  初始值为false
     * @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;
    }

}


自定义视图之————安卓图库缩放拖拽的完整实现

标签:

原文地址:http://my.oschina.net/u/2282721/blog/487788

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!