自定义ImageView: 实现自由缩放 ,自由移动缩放后的图片 .双击放大与缩小图片 兼容ViewPager

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/zhf6751134/article/details/85002140

直接撸代码, 复制就能用

package com.zhf.baselibrary.view;

import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Matrix;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.support.v4.view.ViewPager;
import android.util.AttributeSet;
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;

/**
 * 自定义ImageView:
 * 1. 初步实现多点触控、自由缩放 ,处理图片自由缩放出现的间隙  默认
 * 2. 多点触控之自由移动缩放后的图片
 * 3. 双击放大与缩小图片
 * 4. 兼容ViewPager
 */

public class ZoomImageView extends ImageView implements
        OnGlobalLayoutListener, OnScaleGestureListener, OnTouchListener {

    private Context context;    //上下文
    private boolean mOnce = false;//是否执行了一次

    /**
     * 初始缩放的比例
     */
    private float initScale;
    /**
     * 缩放比例
     */
    private float midScale;
    /**
     * 可放大的最大比例
     */
    private float maxScale;
    /**
     * 可放大的最大比例
     */
    private float minScale;
    /**
     * 缩放矩阵
     */
    private Matrix scaleMatrix;

    /**
     * 缩放的手势监控类
     */
    private ScaleGestureDetector mScaleGestureDetector;


    /**
     * 上一次移动的手指个数,也可以说是多点个数
     */
    private int mLastPoint;
    /**
     * 上次的中心点的x位置
     */
    private float mLastX;
    /**
     * 上一次中心点的y位置
     */
    private float mLastY;
    /**
     * 一个临界值,即是否触发移动的临界值
     */
    private float mScaleSlop;
    /**
     * 是否可移动
     */
    private boolean isCanDrag = false;


    /**
     * 监测各种手势事件,例如双击
     */
    private GestureDetector mGestureDetector;
    /**
     * 是否正在执行双击缩放
     */
    private boolean isAutoScale;
///////////////////////////////////////////////////////////////////////////////

    /**
     * 自定义是否 自由缩放
     */
    private boolean autoZoom = false;
    /**
     * 最大放大倍数
     */
    private int maxRatio = 4;
    /**
     * 最小缩小倍数
     */
    private int minRatio = 2;


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

    public ZoomImageView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public ZoomImageView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        this.context = context;
        //用来操作 image的  矩阵
        scaleMatrix = new Matrix();
        setScaleType(ScaleType.MATRIX);
        //手势回调
        mScaleGestureDetector = new ScaleGestureDetector(context, this);
        //触摸回调
        setOnTouchListener(this);
        //获得系统给定的触发移动效果的临界值
        mScaleSlop = ViewConfiguration.get(context).getScaledTouchSlop();
    }

    /**
     * 设置是否支持双击放大
     */
    public void setDoubleZoom(boolean doubleZoom) {
        //双击放大2倍 缩小
        if (doubleZoom) {
            mGestureDetector = new GestureDetector(context, new MySimpleOnGestureListener());
        } else {
            mGestureDetector = null;
        }
    }

    /**
     * 设置是否支持手势缩放
     */
    public void setAutoZoom(boolean autoZoom) {
        this.autoZoom = autoZoom;
    }

    /**
     * 该方法在view与window绑定时被调用,且只会被调用一次,其在view的onDraw方法之前调用
     */
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        //注册监听器
        getViewTreeObserver().addOnGlobalLayoutListener(this);
    }

    /**
     * 该方法在view被销毁时被调用
     */
    @SuppressLint("NewApi")
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        //取消监听器
        getViewTreeObserver().removeOnGlobalLayoutListener(this);
    }

    /**
     * 当一个view的布局加载完成或者布局发生改变时,OnGlobalLayoutListener会监听到,调用该方法
     * 因此该方法可能会被多次调用,需要在合适的地方注册和取消监听器
     * <p>
     * 图片居中全屏View显示
     */
    public void onGlobalLayout() {
        if (!mOnce) {
            //获得当前view的Drawable
            Drawable d = getDrawable();

            if (d == null) {
                return;
            }

            //获得Drawable的宽和高
            int dw = d.getIntrinsicWidth();
            int dh = d.getIntrinsicHeight();

            //获取当前view的宽和高
            int width = getWidth();
            int height = getHeight();

            //缩放的比例,scale可能是缩小的比例也可能是放大的比例,看它的值是大于1还是小于1
            float scale = 1.0f;

            //如果仅仅是图片宽度比view宽度大,则应该将图片按宽度缩小
            if (dw > width && dh < height) {
                scale = width * 1.0f / dw;
            }
            //如果图片和高度都比view的大,则应该按最小的比例缩小图片
            if (dw > width && dh > height) {
                scale = Math.min(width * 1.0f / dw, height * 1.0f / dh);
            }
            //如果图片宽度和高度都比view的要小,则应该按最小的比例放大图片
            if (dw < width && dh < height) {
                scale = Math.min(width * 1.0f / dw, height * 1.0f / dh);
            }
            //如果仅仅是高度比view的大,则按照高度缩小图片即可
            if (dw < width && dh > height) {
                scale = height * 1.0f / dh;
            }

            //初始化缩放的比例
            initScale = scale;
            midScale = initScale * 2;
            maxScale = initScale * maxRatio;
            minScale = initScale / minRatio;

            //移动图片到达view的中心
            int dx = width / 2 - dw / 2;
            int dy = height / 2 - dh / 2;
            scaleMatrix.postTranslate(dx, dy);

            //缩放图片
            scaleMatrix.postScale(initScale, initScale, width / 2, height / 2);

            setImageMatrix(scaleMatrix);
            mOnce = true;
        }
    }

    /**
     * 获取当前已经缩放的比例
     *
     * @return 因为x方向和y方向比例相同,所以只返回x方向的缩放比例即可
     */
    private float getDrawableScale() {

        float[] values = new float[9];
        scaleMatrix.getValues(values);

        return values[Matrix.MSCALE_X];

    }

    /**
     * 缩放手势开始时调用该方法
     */
    public boolean onScaleBegin(ScaleGestureDetector detector) {
        //返回为true,则缩放手势事件往下进行,否则到此为止,即不会执行onScale和onScaleEnd方法
        return autoZoom;
    }

    /**
     * 缩放手势完成后调用该方法
     */
    public void onScaleEnd(ScaleGestureDetector detector) {

    }

    /**
     * 缩放手势进行时调用该方法
     * <p>
     * 缩放范围:initScale~maxScale
     */
    public boolean onScale(ScaleGestureDetector detector) {

        if (getDrawable() == null) {
            return true;//如果没有图片,下面的代码没有必要运行
        }

        //获取当前已经缩放的比例
        float scale = getDrawableScale();
        //获取当前缩放因子,指的是手指缩放手势  用户预缩放的值
        float scaleFactor = detector.getScaleFactor();

        //如果当前缩放比例比最大比例小,且缩放因子大于1,说明想放大,这是被允许的,因为还还可以再放大。
        //如果当前缩放比例比最小比例大,且缩放因子小于1,说明想缩小,这也是被允许的。
        if ((scale < maxScale
                && scaleFactor > 1.0f)
                || (scale > minScale
                && scaleFactor < 1.0f)) {

            //如果缩小的范围比允许的最小范围还要小,就重置缩放因子为当前的状态的因子
            if (scale * scaleFactor < minScale && scaleFactor < 1.0f) {
                scaleFactor = minScale / scale;
            }

            //如果缩小的范围比允许的最小范围还要小,就重置缩放因子为当前的状态的因子
            if (scale * scaleFactor > maxScale && scaleFactor > 1.0f) {
                scaleFactor = maxScale / scale;
            }
            //scaleMatrix.postScale(1.0f, 1.0f, getWidth() / 2, getHeight() / 2);
            scaleMatrix.postScale(scaleFactor, scaleFactor, detector.getFocusX(), detector.getFocusY());

            checkBoderAndCenter();//处理缩放后图片边界与屏幕有间隙或者不居中的问题

            setImageMatrix(scaleMatrix);//千万不要忘记设置这个,我总是忘记
        }
        return true;
    }

    /**
     * 实现双击放大或者缩小图片。用到的知识点就是GestureDetector,用它来监测双击事件。
     * 至于双击后怎么缩放图片,相信在前面几篇文章中,你都已经很熟悉了。
     * 但是难点是,我们要求双击后缓慢的放大或者缩小,而不是一下子就放大到或者缩小到目标值。
     * 这里就要结合线程来处理了。其实处理的逻辑也很简单:
     * 比如说放大,我们每隔一段时间,就对图片进行放大一次,然后看看是不是达到要求的放大比例了,
     * 如果达到了就终止,否则继续放大,直到达到要求为止。
     */
    private class MySimpleOnGestureListener extends GestureDetector.SimpleOnGestureListener {
        public boolean onDoubleTap(MotionEvent e) {
            //如果正在执行双击缩放,直接跳过
            if (isAutoScale) {
                return true;
            }
            float x = e.getX();
            float y = e.getY();
            //获得当前的缩放比例
            float scale = getDrawableScale();
            //如果比midScale小,一律放大,否则一律缩小为initScale
            if (scale < midScale) {
                //  scaleMatrix.postScale(midScale/scale,midScale/scale, x, y);
                //  setImageMatrix(scaleMatrix);
                postDelayed(new AutoScaleRunnable(midScale, x, y), 16);
                isAutoScale = true;
            } else {
                //  scaleMatrix.postScale(initScale/scale,initScale/scale, x, y);
                //  setImageMatrix(scaleMatrix);
                postDelayed(new AutoScaleRunnable(initScale, x, y), 16);
                isAutoScale = true;
            }
            return true;
        }
    }

    /**
     * 将 双击缩放使用梯度
     *
     * @author fuly1314
     */
    private class AutoScaleRunnable implements Runnable {

        private float targetScale;//缩放的目标值
        private float x;
        private float y;//缩放的中心点
        private float tempScale;
        private float BIGGER = 1.07F;
        private float SMALL = 0.93F;//缩放的梯度

        public AutoScaleRunnable(float targetScale, float x, float y) {
            super();
            this.targetScale = targetScale;
            this.x = x;
            this.y = y;

            if (getDrawableScale() < targetScale) {
                tempScale = BIGGER;
            }
            if (getDrawableScale() > targetScale) {
                tempScale = SMALL;
            }
        }

        public void run() {
            scaleMatrix.postScale(tempScale, tempScale, x, y);
            checkBoderAndCenter();
            setImageMatrix(scaleMatrix);
            float scale = getDrawableScale();
            if ((scale < targetScale && tempScale > 1.0f) || (scale > targetScale && tempScale < 1.0f)) {
                postDelayed(this, 16);
            } else {
                scaleMatrix.postScale(targetScale / scale, targetScale / scale, x, y);
                checkBoderAndCenter();
                setImageMatrix(scaleMatrix);
                isAutoScale = false;
            }
        }
    }

    /**
     * 处理缩放后图片边界与屏幕有间隙或者不居中的问题
     */
    private void checkBoderAndCenter() {
        RectF rectf = getDrawableRectF();
        int width = getWidth();
        int height = getHeight();

        float delaX = 0;
        float delaY = 0;

        if (rectf.width() >= width) {
            if (rectf.left > 0) {
                delaX = -rectf.left;
            }

            if (rectf.right < width) {
                delaX = width - rectf.right;
            }
        }

        if (rectf.height() >= height) {
            if (rectf.top > 0) {
                delaY = -rectf.top;
            }
            if (rectf.bottom < height) {
                delaY = height - rectf.bottom;
            }
        }

        if (rectf.width() < width) {
            delaX = width / 2 - rectf.right + rectf.width() / 2;
        }

        if (rectf.height() < height) {
            delaY = height / 2 - rectf.bottom + rectf.height() / 2;
        }

        scaleMatrix.postTranslate(delaX, delaY);
    }

    /**
     * 获取图片根据矩阵变换后的四个角的坐标,即left,top,right,bottom
     *
     * @return
     */
    private RectF getDrawableRectF() {
        Matrix matrix = scaleMatrix;
        RectF rectf = new RectF();
        Drawable d = getDrawable();
        if (d != null) {

            rectf.set(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());
        }
        matrix.mapRect(rectf);
        return rectf;
    }


    /**
     * 监听触摸事件
     */
    public boolean onTouch(View v, MotionEvent event) {

        if (mGestureDetector != null && mGestureDetector.onTouchEvent(event)) {
            return true;
        }

        if (mScaleGestureDetector != null) {
            //将触摸事件传递给手势缩放这个类
            mScaleGestureDetector.onTouchEvent(event);
        }

        //获得多点个数,也叫屏幕上手指的个数
        int pointCount = event.getPointerCount();

        float x = 0;
        float y = 0;//中心点的x和y

        for (int i = 0; i < pointCount; i++) {
            x += event.getX(i);
            y += event.getY(i);
        }

        //求出中心点的位置
        x /= pointCount;
        y /= pointCount;

        //如果手指的数量发生了改变,则不移动
        if (mLastPoint != pointCount) {
            isCanDrag = false;
            mLastX = x;
            mLastY = y;

        }
        mLastPoint = pointCount;
        RectF rectf = getDrawableRectF();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                if (rectf.width() > getWidth() + 0.01 || rectf.height() > getHeight() + 0.01) {
                    //请求父类不要拦截ACTION_DOWN事件
                    if (getParent() instanceof ViewPager)
                        this.getParent().requestDisallowInterceptTouchEvent(true);
                }
                break;
            case MotionEvent.ACTION_MOVE:
                if (rectf.width() > getWidth() + 0.01 || rectf.height() > getHeight() + 0.01) {
                    //请求父类不要拦截ACTION_MOVE事件
                    if (getParent() instanceof ViewPager)
                        this.getParent().requestDisallowInterceptTouchEvent(true);
                }
                //求出移动的距离
                float dx = x - mLastX;
                float dy = y - mLastY;

                if (!isCanDrag) {
                    isCanDrag = isCanDrag(dx, dy);
                }

                if (isCanDrag) {
                    //如果图片能正常显示,就不需要移动了
                    if (rectf.width() <= getWidth()) {
                        dx = 0;
                    }
                    if (rectf.height() <= getHeight()) {
                        dy = 0;
                    }

                    //开始移动
                    scaleMatrix.postTranslate(dx, dy);
                    //处理移动后图片边界与屏幕有间隙或者不居中的问题
                    checkBoderAndCenterWhenMove();
                    setImageMatrix(scaleMatrix);
                }

                mLastX = x;
                mLastY = y;


                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                mLastPoint = 0;
                break;

        }

        return true;
    }

    /**
     * 处理移动后图片边界与屏幕有间隙或者不居中的问题
     * 这跟我们前面写的代码很像
     */
    private void checkBoderAndCenterWhenMove() {

        RectF rectf = getDrawableRectF();

        float delaX = 0;
        float delaY = 0;
        int width = getWidth();
        int height = getHeight();

        if (rectf.width() > width && rectf.left > 0) {
            delaX = -rectf.left;
        }
        if (rectf.width() > width && rectf.right < width) {
            delaX = width - rectf.right;
        }
        if (rectf.height() > height && rectf.top > 0) {
            delaY = -rectf.top;
        }
        if (rectf.height() > height && rectf.bottom < height) {
            delaY = height - rectf.bottom;
        }
        scaleMatrix.postTranslate(delaX, delaY);
    }

    /**
     * 判断是否触发移动效果
     *
     * @param dx
     * @param dy
     * @return
     */
    private boolean isCanDrag(float dx, float dy) {
        return Math.sqrt(dx * dx + dy * dy) > mScaleSlop;
    }
}

我们不生产代码,我们是互联网的搬运工,感谢作者:https://www.cnblogs.com/fuly550871915/

猜你喜欢

转载自blog.csdn.net/zhf6751134/article/details/85002140