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

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

    }

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


源代码


参考:


相关文章
|
4月前
|
机器学习/深度学习 算法 人机交互
|
10月前
|
容器
Flutter的AspectRatio控件实现视频播放、图片播放按照长宽比缩放
Flutter的AspectRatio控件实现视频播放、图片播放按照长宽比缩放
|
iOS开发
IOS手指控制图片的缩放
IOS手指控制图片的缩放
45 0
|
Go Android开发
|
C++
duilib corner属性的贴图技巧——让图片自动贴到控件的的某一边或者一角并自适应控件的大小
转载请说明原出处,谢谢~~          Duilib给控件贴图功能可以附带多个属性,各个属性的配合可以达到许多效果。以下是duilib支持的所有贴图属性: 贴图描述:          Duilib的表现力丰富很大程度上得益于贴图描述的简单强大。
1715 0
|
Android开发 Java 数据格式
Android视频播放器屏幕左侧边随手指上下滑动亮度调节变暗变亮原理实现(2):后续改进
 Android视频播放器屏幕左侧边随手指上下滑动亮度调节变暗变亮原理实现(2):后续改进 附录文章1虽然实现了在屏幕左半边随手指上滑/下滑实现明暗度的调节,但是有一个不完美的地方:当手指在屏幕左半边水平左滑/右滑时候,也一样会触发明暗度的调节。
985 0