上一节我们是通过重写自定义View的onTouchEvent方法来实现我们的图片放大缩小功能的,我们也发现现在app中,图片预览功能很常见的,用户基本已经形成条件反射,看到小图,点击看大图,看到大图两个手指开始进行放大,放大后,开始移动到指定部位,实际上,Android系统本身也是有手势检测这个类来帮助我们实现相关功能的。




首先了解要用到的几个类:
Matrix
在图像处理方面,主要是用于平面的缩放、平移、旋转等操作。Android中的Matrix是一个3 x 3的矩阵,可以使用setValues(float[] values)进行初始化,其内容如下:

{  
   MSCALE_X, MSKEW_X, MTRANS_X,    
   MSKEW_Y, MSCALE_Y, MTRANS_Y,    
    MPERSP_0, MPERSP_1, MPERSP_2    
 }


Matrix的对图像的处理可分为四类基本变换:

Translate           平移变换

Rotate                旋转变换

Scale                  缩放变换

Skew                  倾斜变换


针对每种变换,Android提供了pre、set和post三种操作方式。其中

set用于设置Matrix中的值。

pre是先乘,因为矩阵的乘法不满足交换律,因此先乘、后乘必须要严格区分。先乘相当于矩阵运算中的右乘。

post是后乘,因为矩阵的乘法不满足交换律,因此先乘、后乘必须要严格区分。后乘相当于矩阵运算中的左乘。


具体的用法我们可以看下api说明



GestureDetector 

一共有三个监听器——OnDoubleTapListener、OnGestureListener 、SimpleOnGestureListener。具体的可以自己看API,能够捕捉到长按、双击什么的


ScaleGestureDetector 

嗯,有点像继承来的,其实不是的,独立的一个类,用于检测缩放的手势





下面做一个图片浏览器的效果,使用ViewPager让用户可以滑动切换图片,通过手势实现用户对图片的放大缩小



扩展ImageView,当图片加载时,将图片在屏幕中居中;图片宽或高大于屏幕的,缩小至屏幕大小;可以自由对图片进行放大或缩小


先把代码贴上,然后详细的说明


public class ZoomImageView extends ImageView implements
 
   
        ViewTreeObserver.OnGlobalLayoutListener
 
   

 
   
{
 
   
    public static final float SCALE_MAX = 4.0f;
 
   
    /**
 
   
     * 初始化时的缩放比例,如果图片宽或高大于屏幕,此值将小于0
 
   
     */
 
   
    private float initScale = 1.0f;
 
   

 
   
    /**
 
   
     * 用于存放矩阵的9个值
 
   
     */
 
   
    private final float[] matrixValues = new float[9];
 
   
    private boolean once = true;
 
   
    /**
 
   
     * 缩放的手势检测
 
   
     */
 
   
    private ScaleGestureDetector mScaleGestureDetector;
 
   
    private final Matrix mScaleMatrix = new Matrix();
 
   

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

 
   
    public ZoomImageView(Context context, AttributeSet attrs) {
 
   
        super(context, attrs);
 
   
        super.setScaleType(ScaleType.MATRIX);
 
   
        initGestureDector(context);
 
   

 
   
    }
 
   

 
   
    /**
 
   
     * 初始化缩放手势检测类
 
   
     * @param context
 
   
     */
 
   
    private void initGestureDector(Context context) {
 
   
        mScaleGestureDetector = new ScaleGestureDetector(context,
 
   
                new OnScaleGestureListener() {
 
   

 
   
                    @Override
 
   
                    public void onScaleEnd(ScaleGestureDetector detector) {
 
   
                    }
 
   

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

 
   
                    @Override
 
   
                    public boolean onScale(ScaleGestureDetector detector) {
 
   
                        float scale = getScale();
 
   
                        float scaleFactor = detector.getScaleFactor();
 
   

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

 
   
                        /**
 
   
                         * 缩放的范围控制
 
   
                         */
 
   
                        if ((scale < SCALE_MAX && scaleFactor > 1.0f)
 
   
                                || (scale > initScale && scaleFactor < 1.0f)) {
 
   
                            /**
 
   
                             * 最大值最小值判断
 
   
                             */
 
   
                            if (scaleFactor * scale < initScale) {
 
   
                                scaleFactor = initScale / scale;
 
   
                            }
 
   
                            if (scaleFactor * scale > SCALE_MAX) {
 
   
                                scaleFactor = SCALE_MAX / scale;
 
   
                            }
 
   
                            /**
 
   
                             * 设置缩放比例
 
   
                             */
 
   
                            mScaleMatrix.postScale(scaleFactor, scaleFactor,
 
   
                                    detector.getFocusX(), detector.getFocusY());
 
   
                            checkBorder();
 
   
                            setImageMatrix(mScaleMatrix);
 
   
                        }
 
   
                        return true;
 
   
                    }
 
   
                });
 
   
    }
 
   

 
   
    /**
 
   
     * 在缩放时,进行图片显示范围的控制
 
   
     */
 
   
    private void checkBorder() {
 
   
        RectF rect = getMatrixRectF();
 
   
        float deltaX = 0;
 
   
        float deltaY = 0;
 
   

 
   
        int width = getWidth();
 
   
        int height = getHeight();
 
   

 
   
        // 如果宽或高大于屏幕,则控制范围
 
   
        if (rect.width() >= width) {
 
   
            if (rect.left > 0) {
 
   
                deltaX = -rect.left;
 
   
            }
 
   
            if (rect.right < width) {
 
   
                deltaX = width - rect.right;
 
   
            }
 
   
        }
 
   
        if (rect.height() >= height) {
 
   
            if (rect.top > 0) {
 
   
                deltaY = -rect.top;
 
   
            }
 
   
            if (rect.bottom < height) {
 
   
                deltaY = height - rect.bottom;
 
   
            }
 
   
        }
 
   
        // 如果宽或高小于屏幕,则让其居中
 
   
        if (rect.width() < width) {
 
   
            deltaX = width * 0.5f - rect.right + 0.5f * rect.width();
 
   
        }
 
   
        if (rect.height() < height) {
 
   
            deltaY = height * 0.5f - rect.bottom + 0.5f * rect.height();
 
   
        }
 
   
        mScaleMatrix.postTranslate(deltaX, deltaY);
 
   

 
   
    }
 
   

 
   
    /**
 
   
     * 根据当前图片的Matrix获得图片的范围
 
   
     * 
 
   
     * @return
 
   
     */
 
   
    private RectF getMatrixRectF() {
 
   
        Matrix matrix = mScaleMatrix;
 
   
        RectF rect = new RectF();
 
   
        Drawable d = getDrawable();
 
   
        if (null != d) {
 
   
            rect.set(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());
 
   
            matrix.mapRect(rect);
 
   
        }
 
   
        return rect;
 
   
    }
 
   

 
   
    @Override
 
   
    public boolean onTouchEvent(MotionEvent event) {
 
   
        mScaleGestureDetector.onTouchEvent(event);
 
   
        return true;
 
   
    }
 
   

 
   
    /**
 
   
     * 获得当前的缩放比例
 
   
     * 
 
   
     * @return
 
   
     */
 
   
    public final float getScale() {
 
   
        mScaleMatrix.getValues(matrixValues);
 
   
        return matrixValues[Matrix.MSCALE_X];
 
   
    }
 
   

 
   
    @Override
 
   
    protected void onAttachedToWindow() {
 
   
        super.onAttachedToWindow();
 
   
        getViewTreeObserver().addOnGlobalLayoutListener(this);
 
   
    }
 
   

 
   
    @SuppressWarnings("deprecation")
 
   
    @Override
 
   
    protected void onDetachedFromWindow() {
 
   
        super.onDetachedFromWindow();
 
   
        getViewTreeObserver().removeGlobalOnLayoutListener(this);
 
   
    }
 
   

 
   
    @Override
 
   
    public void onGlobalLayout() {
 
   
        if (once) {
 
   
            Drawable d = getDrawable();
 
   
            if (d == null)
 
   
                return;
 
   
            int width = getWidth();
 
   
            int height = getHeight();
 
   
            // 拿到图片的宽和高
 
   
            int dw = d.getIntrinsicWidth();
 
   
            int dh = d.getIntrinsicHeight();
 
   
            float scale = 1.0f;
 
   
            // 如果图片的宽或者高大于屏幕,则缩放至屏幕的宽或者高
 
   
            if (dw > width || dh > height) {
 
   
                scale = Math.min(width * 1.0f / dw, height * 1.0f / dh);
 
   
            }
 
   
            initScale = scale;
 
   

 
   
            mScaleMatrix.postTranslate((width - dw) / 2, (height - dh) / 2);
 
   
            mScaleMatrix.postScale(scale, scale, getWidth() / 2,
 
   
                    getHeight() / 2);
 
   
            // 图片移动至屏幕中心
 
   
            setImageMatrix(mScaleMatrix);
 
   
            once = false;
 
   
        }
 
   

 
   
    }
 
   

 
   
}




在onGlobalLayout的回调中,根据图片的宽和高以及屏幕的宽和高,对图片进行缩放以及移动至屏幕的中心。如果图片很小,那就正常显示

重写onTouchEvent方法,将触碰事件交给GestureDetector和ScaleGestureDetector处理


创建ScaleGestureDetector对象,在onScale的回调中对图片进行缩放的控制,首先进行缩放范围的判断,然后设置mScaleMatrix的scale值

但现在还存在一些问题:

1、缩放的中心点,我们设置是固定的,只在屏幕中间

2、放大后,无法移动图片

下面就解决这些问题

设置缩放中心

1、单纯的设置缩放中心

仅仅是设置中心很简单,直接在onScale中修改下中心点 :


mScaleMatrix.postScale(scaleFactor, scaleFactor,  
                     detector.getFocusX(), detector.getFocusX());  
             setImageMatrix(mScaleMatrix);


但是,随意的中心点放大、缩小,会导致图片的位置的变化,最终导致,图片宽高大于屏幕时,图片与屏幕间出现白边;图片小于屏幕,但是不居中。

2、控制缩放时图片显示的范围

所以我们在缩放的时候需要手动控制下范围:


private void checkBorder() {
 
 

 
 
        RectF rect = getMatrixRectF();
 
 
        float deltaX = 0;
 
 
        float deltaY = 0;
 
 

 
 
        int width = getWidth();
 
 
        int height = getHeight();
 
 

 
 
        // 如果宽或高大于屏幕,则控制范围
 
 
        if (rect.width() >= width) {
 
 
            if (rect.left > 0) {
 
 
                deltaX = -rect.left;
 
 
            }
 
 
            if (rect.right < width) {
 
 
                deltaX = width - rect.right;
 
 
            }
 
 
        }
 
 
        if (rect.height() >= height) {
 
 
            if (rect.top > 0) {
 
 
                deltaY = -rect.top;
 
 
            }
 
 
            if (rect.bottom < height) {
 
 
                deltaY = height - rect.bottom;
 
 
            }
 
 
        }
 
 
        // 如果宽或高小于屏幕,则让其居中
 
 
        if (rect.width() < width) {
 
 
            deltaX = width * 0.5f - rect.right + 0.5f * rect.width();
 
 
        }
 
 
        if (rect.height() < height) {
 
 
            deltaY = height * 0.5f - rect.bottom + 0.5f * rect.height();
 
 
        }
 
 

 
 
        mScaleMatrix.postTranslate(deltaX, deltaY);
 
 

 
 
    }


现在图片缩放的功能就实现了

自定义View利用手势检测实现图片放大缩小_图片


源代码


参考:


http://www.360doc.com/content/14/0610/21/16623487_385518125.shtml