整理自 鸿洋大神的慕课网视频

加了很多自己理解的注注释

package MyView;
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.View;
import android.view.ViewConfiguration;
import android.view.ViewParent;
import android.view.ViewTreeObserver;
import android.widget.ImageView;

/**
 * 图片预览与多点触控ImageView
 * <p>
 * <p>
 * Created by wjc on 2017/3/8.
 */

public class ZoomImageView extends ImageView implements ViewTreeObserver.OnGlobalLayoutListener, ScaleGestureDetector.OnScaleGestureListener, View.OnTouchListener {/**
     * 因为缩放只需在加载图片完成后进行一次,所以需要设置一个标记
     */
    private boolean mOnce;

    /**
     * 初始化时缩放的值,也就是根据图片于控件宽高计算得到的缩放比例
     */
    private float mInitScale;

    /**
     * 双击放大的的scale比例
     */
    private float mMidScale;

    /**
     * 允许放大的最大缩放比例
     */
    private float mMaxScale;

    /**
     * 缩放矩阵
     */
    private Matrix mScaleMatrix;

    /**
     * 可以获取到当前用户手势的 缩放比例
     */
    private ScaleGestureDetector mScaleGestureDetector;

//------------------自由移动所需变量
    /**
     * 记录上一次多点触控的点的数量,因为4个手指变为2个手指的时候,
     * 触控的中心可能就瞬间发生很大的跳跃变化,如果只是根据触控中心位置实时移动图片,用户体验差
     */
    private int mLastPointerCount;

    private float mLastX; //记录上次多指的中心点

    private float mlastY;

    private int mTouchSlop;

    private boolean isCanDrag;

    private RectF matrixRectF;

    private boolean isCheckLeftAndRight;

    private boolean isCheckTopAndBottom;

//-----------------双击放大与缩小

    private GestureDetector mGestureDetector;

    private boolean isAutoScale; //当前是否处在双击后的缩放过程当中

    public ZoomImageView(Context context) {this(context, null);
    }public ZoomImageView(Context context, AttributeSet attrs) {this(context, attrs, 0);
    }public ZoomImageView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);

        mScaleMatrix = new Matrix();

        setScaleType(ScaleType.MATRIX);//覆盖xml的scaleType 因为这里的缩放是建立在ScaleType.MATRIX的

        mScaleGestureDetector = new ScaleGestureDetector(context, this);

        setOnTouchListener(this);

        mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();

        mGestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() {/**
             *
             * 双击的图片放大缩小,注意边界处理
             * 如果双击直接放大到目标大小,中间是没有变化过程的,会非常突兀,可以使用runnable,分成多次,延时post
             *
             */
            @Override
            public boolean onDoubleTap(MotionEvent e) {/**
                 * 当前仍在缩放 就直接return;
                 */
                if (isAutoScale) {return true;
                }float x = e.getX();
                float y = e.getY();
                float currentScale = getScale();

                /**
                 * 两个特殊情况:
                 * 当前为 mMidScale 会缩小为mInitScale
                 * 当前为 mInitScale 会放大为mMidScale
                 * 也就是说  不会出现双击后无变化的情况。mTargetScale不会等于getScale;
                 */
                if (currentScale < mMidScale) {
//                    mScaleMatrix.postScale(mMidScale / currentScale, mMidScale / currentScale, x, y); //这是直接缩放,不可取

                    postDelayed(new AutoScaleRunnable(mMidScale, x, y), 16);
                    isAutoScale = true;

                } else {//                    mScaleMatrix.postScale(mInitScale / currentScale, mInitScale / currentScale, x, y); //
                    postDelayed(new AutoScaleRunnable(mInitScale, x, y), 16);
                    isAutoScale = true;
                }return true;
            }});

    }/**
     * 自动缓慢放大与缩小
     */
    private class AutoScaleRunnable implements Runnable {/**
         * 缩放的目标值以及缩放的中心点位置(双击位置)
         */
        private float mTargetScale;
        private float x;
        private float y;

        private final float BIGGER = 1.07f;
        private final float SMALLER = 0.93f;

        private float tmpScale;

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

            //确定当前双击 每次的scale系数,放大还是缩小
            if (mTargetScale > getScale()) {tmpScale = BIGGER;
            } else {tmpScale = SMALLER;
            }}@Override
        public void run() {/**
             * 进行缩放
             */
            mScaleMatrix.postScale(tmpScale, tmpScale, x, y);
            checkBorderAndCenterWhenScale();
            setImageMatrix(mScaleMatrix);

            float currentScale = getScale();

            /**
             *    判断本次缩放之后如果仍未达到目标值,接着进行下一循环的缩放,判断
             */
            if (tmpScale > 1.0f && currentScale < mTargetScale || tmpScale < 1.0f && currentScale > mTargetScale) {postDelayed(this, 16);

            } else {/**
                 * 临界判定
                 *
                 * 如果本次缩放之后已经达到或者超过了目标值
                 * 那么直接缩放到目标值
                 *
                 */
                float scale = mTargetScale / currentScale;
                mScaleMatrix.postScale(scale, scale, x, y);
                checkBorderAndCenterWhenScale();
                setImageMatrix(mScaleMatrix);

                isAutoScale = false;

            }}}/**
     * 注册与取消注册注册 OnGlobalLayoutListener
     */
    @Override
    protected void onAttachedToWindow() {super.onAttachedToWindow();
        getViewTreeObserver().addOnGlobalLayoutListener(this);

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

    }/**
     * 全局布局完成后 调用以下OnGlobalLayoutListener的实现方法
     * 在这里获取控件的宽高是比较合适的
     * 获取ImageView加载完成的图片,并且比较图片大小与屏幕大小 根据结果进行缩放
     * 缩放之后,能居中显示完整的图片,并且宽或者高顶到ImageView的边
     */
    @Override
    public void onGlobalLayout() {if (!mOnce) {int width = getWidth(); //获取当前控件的宽和高
            int height = getHeight();

            Drawable d = getDrawable();
            if (d == null) {return;
            }int dw = d.getIntrinsicWidth(); //获得加载完成的图片宽高属性
            int dh = d.getIntrinsicHeight();

            float scale = 1.0f;

            //四种情况

            /**
             * 缩放策略
             */
            if (dw > width && dh < height) {scale = width * 1.0f / dw;

            } else if (dw < width && dh > height) {scale = height * 1.0f / dh;
            } else {scale = Math.min(width * 1.0f / dw, height * 1.0f / dh);
            }mInitScale = scale;
            mMidScale = mInitScale * 2;
            mMaxScale = mInitScale * 4;

            /**
             * 将图片移动到控件的中心
             */
            int translateX = (width - dw) / 2;
            int translateY = (height - dh) / 2;

            mScaleMatrix.postTranslate(translateX, translateY);
            mScaleMatrix.postScale(mInitScale, mInitScale, width / 2, height / 2);
            setImageMatrix(mScaleMatrix);

            mOnce = true;
        }}/**
     * 获取当前的总体缩放比例(在缩放矩阵中可以查到)
     */
    public float getScale() {float[] values = new float[9];
        mScaleMatrix.getValues(values);
        return values[Matrix.MSCALE_X];

    }/**
     * ScaleGestureDetector的三个要实现的方法
     * 监听 由nTouch传递过来的多点触控的缩放手势
     * <p>
     * 本次onScale完成后的最终总体缩放结果,就是
     * 原来的总体缩放结果getScale的值乘以本次的缩放因子scaleFactor
     */
    @Override
    public boolean onScale(ScaleGestureDetector detector) {float scale = getScale();
        float scaleFactor = detector.getScaleFactor();//获得当前手势的缩放比例,可能大于1 也可能小于1

        if (getDrawable() == null) {return true;
        }//注意判断在每一次调用onScare前后,总的缩放比例都应该在 mInitScale和mMaxScale 之间
        if (scale < mMaxScale && scaleFactor > 1.0f || scale > mInitScale && scaleFactor < 1.0f) {//在满足前面的条件下,如果在这次缩放之后 比例超出了范围  就要调整scaleFactor
            if (scale * scaleFactor < mInitScale) {/**
                 * 修正scaleFactor使最终的缩放结果为mInitScale,不能再小了
                 * 理论上 使scaleFactor直接等于1也可以,也就是在本次缩放会触及边界的情况下,取消本次缩放
                 * 但是如果是修正缩放结果为刚好达到边界,效果自然是最好的
                 */
                scaleFactor = mInitScale / scale; // 使最终的缩放结果为mInitScale,不能再小了

            }if (scale * scaleFactor > mMaxScale) {scaleFactor = mMaxScale / scale; // 使最终的缩放结果为mMaxScale,不能再大了

            }/**
             * 后两个参数是缩放的中心点,将其设为当前缩放手势的中心点
             */
            mScaleMatrix.postScale(scaleFactor, scaleFactor, detector.getFocusX(),
                    detector.getFocusY());

            /**
             *
             * 如果直接设置缩放中心点为手势点的话,几次缩放之后不仅图片中心会偏移,屏幕上可能还会出现留白
             * 所以需要在缩放之前 即时检测调整,并且将调整后的偏移一起post到mScaleMatrix中
             */
            checkBorderAndCenterWhenScale();

            setImageMatrix(mScaleMatrix);

        }return true;
    }/**
     * 在缩放的时候进行边界控制 以及位置控制
     * 总体思路是 图片较大的时候 处理留白 , 图片较小的时候 处理居中
     */
    private void checkBorderAndCenterWhenScale() {RectF rectF = getMatrixRectF();

        //为了修正需要偏移的值
        float deltaX = 0;
        float deltaY = 0;

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

        /**
         * 进行边界判断
         * 当图片宽度大于控件宽度,而控件左右有留白
         * 当图片高度大于控件高度,而控件上下有留白
         * 都是需要进行偏移处理覆盖留白的
         */
        if (rectF.width() >= width) {if (rectF.left > 0) {deltaX = -rectF.left;

            }if (rectF.right < width) {deltaX = width - rectF.right;
            }}if (rectF.height() >= height) {if (rectF.top > 0) {deltaY = -rectF.top;
            }if (rectF.bottom < height) {deltaY = height - rectF.bottom;
            }}/**
         * 如果图片的宽度或者高度小于控件的 则进行居中
         * 计算的偏移量的时候要注意此时的图片的边不一定与控件的左边或者上边重合(基本不会重合)
         */

        if (rectF.width() < width) {deltaX = width / 2f - rectF.left - rectF.width() / 2f;

        }if (rectF.height() < height) {deltaY = height / 2f - rectF.top - rectF.height() / 2f;
        }mScaleMatrix.postTranslate(deltaX, deltaY);

    }/**
     * RectF:保存一个矩形的left;top;right;bottom;四个参数,且都是float
     * <p>
     * 获取当前图片的矩形坐标
     */
    private RectF getMatrixRectF() {Matrix matrix = mScaleMatrix;
        RectF rectF = new RectF();

        Drawable d = getDrawable();
        if (d != null) {//设定图片在经过Matrix变换之前的Rect坐标
            rectF.set(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());

            //将rectF坐标进行矩阵变换之后的坐标重新赋值给rectF
            matrix.mapRect(rectF);
        }return rectF;
    }@Override
    public boolean onScaleBegin(ScaleGestureDetector detector) {return true;
    }@Override
    public void onScaleEnd(ScaleGestureDetector detector) {}/**
     * 实现OnTouchListener 并且监听onTouchListener,然后在实现方法onTouch中 将触摸时事件交给ScaleGestureDetector来处理
     * 毕竟是系统的多点触控处理API,很专业 哈哈
     */
    @Override
    public boolean onTouch(View v, MotionEvent event) {//优先处理双击,以免在双击的时候产生缩放或者移动
        if (mGestureDetector.onTouchEvent(event)) {return true;
        }//将多点的scale移交给mScaleGestureDetector
        mScaleGestureDetector.onTouchEvent(event);

        /**
         * 以下是平移操作
         */
        float x = 0;//保存多点触控的中心点
        float y = 0;

        //获取当前的触控点数
        int pointerCount = event.getPointerCount();
        for (int i = 0; i < pointerCount; i++) {x += event.getX(i);
            y += event.getY(i);

        }x /= pointerCount;//算出当前的中心点位置
        y /= pointerCount;

        /**
         * isCanDrag为false有两种情况,一种是触控点数发生了变化,我们只是记录位置
         * 另一种是位移太小
         * 这两种情况 我们都不会postTranslate
         *
         */
        if (mLastPointerCount != pointerCount) {isCanDrag = false;
            mLastX = x;
            mlastY = y;
        }mLastPointerCount = pointerCount;
        RectF rect= getMatrixRectF();
        switch (event.getAction()) {/**
             * 在 down和move的时候进行判断 如果当前图片已经经过人为放大了,那么就请求父控件(Viewpager)
             * 不要拦截touch事件,让图片可以移动查看 而不会滑动到viewpager的下一张
             *
             * 这里加 0.01 是因为避免浮点数计算丢失精确度 确保在mInitScale的情况下可以翻到下一张
             */
            case MotionEvent.ACTION_DOWN:if(rect.width()>getWidth()+0.01||rect.height()>getHeight()+0.01){if(getParent()instanceof ViewPager)getParent().requestDisallowInterceptTouchEvent(true);
            }break;

            case MotionEvent.ACTION_MOVE:if(rect.width()>getWidth()+0.01||rect.height()>getHeight()+0.01){if(getParent()instanceof ViewPager)getParent().requestDisallowInterceptTouchEvent(true);
                }float dx = x - mLastX;
                float dy = y - mlastY;

                if (!isCanDrag) {isCanDrag = isMoveAction(dx, dy);
                }if (isCanDrag) {RectF rectF = getMatrixRectF();
                    if (getDrawable() != null) {isCheckLeftAndRight = isCheckTopAndBottom = true;

                        /**
                         *  图片宽度小于控件宽度,不允许横向移动
                         *  既然不能移动,也就不需要移动时边界控制
                         */

                        if (rectF.width() < getWidth()) {isCheckLeftAndRight = false;
                            dx = 0;
                        }//图片高度小于控件高度,不允许纵向移动
                        if (rectF.height() < getHeight()) {isCheckTopAndBottom = false;
                            dy = 0;
                        }mScaleMatrix.postTranslate(dx, dy);

                        /**
                         * 平移的时候也是需要边界控制的
                         */
                        checkBorderWhenTranslate();
                        setImageMatrix(mScaleMatrix);

                    }}mLastX = x;//移动过程中不断记录上一次的xy
                mlastY = y;
                break;

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

            default:break;

        }return true;
    }/**
     * 移动时 进行边界控制
     */
    private void checkBorderWhenTranslate() {RectF rectF = getMatrixRectF();
        float deltaX = 0;
        float deltaY = 0;

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

        /**
         * 左侧有留白,并且此时图片宽度是大于控件宽度的
         */
        if (rectF.left > 0 && isCheckLeftAndRight) {deltaX = -rectF.left;

        }if (rectF.right < width && isCheckLeftAndRight) {deltaX = width - rectF.right;
        }if (rectF.top > 0 && isCheckTopAndBottom) {deltaY = -rectF.top;

        }if (rectF.bottom < height && isCheckTopAndBottom) {deltaY = height - rectF.bottom;
        }mScaleMatrix.postTranslate(deltaX, deltaY);

    }/**
     * 判断偏移量是否足以触发move
     */
    private boolean isMoveAction(float dx, float dy) {return Math.sqrt(dx * dx + dy * dy) > mTouchSlop;
    }
}

图片的多点触控缩放与移动相关推荐

  1. android 多点触控缩放,Android多点触控(图片的缩放Demo)

    本文主要介绍Android的多点触控,使用了一个图片缩放的实例,来更好的说明其原理.需要实现OnTouchListener接口,重写其中的onTouch方法. 实现效果图: 源代码: 布局文件: ac ...

  2. android 多点触控缩放,【移动开发】Android中图片的多点触控和缩放

    前几天做项目用到相机拍照,之后能对图片进行缩放,拖拽,在此我将其单独抽取出来,后面用到时直接拿来用就行了! 效果图: 注:这里不仅能按钮缩放,还能多点触摸缩放和拖拽功能! 1.布局: android: ...

  3. html5 多点触控 缩放,WebBrowser禁用触摸缩放

    最近做一个WPF触屏的项目,引用到WebBrowser控件,由于是触屏的所以控件里的网页可以缩放,客户提出要求,屏蔽这缩放功能. 于是网上找了很多资料,也换过控件,WebView2 控件使用Micro ...

  4. Android 图片随着手势缩放,平移,并且支持多点触控

    效果图: 现在app中,图片预览功能肯定是少不了的,用户基本已经形成条件反射,看到小图,点击看大图,看到大图两个手指开始进行放大,放大后,开始移动到指定部位~~~ 想要做到图片支持多点触控,自由的进行 ...

  5. 关于android多点触控

    最近项目需要一个多点触控缩放的功能.然后上网查了下资料 总结一下: 首先android sdk版本很重要,比如你在AndroidManifest.xml中指定android:minSdkVersion ...

  6. Android-图片预览(自定义ImageView实现图片缩放,多点触控,自由移动)

    1.回顾 上篇学习了,开源框架中:个性通知效果,7种动画实现:非常不错: 2.重点 (1)实现自定义控件ImageView (2)自由的放大和缩小 (3)自由移动 3.背景 (1) 因为需要使用到 图 ...

  7. Android自定义控件ImageViwe(四)——多点触控实现图片的自由移动

    效果图: 功能 : 可以随手指进行自由移动图片 按照适当的比例设置图片的显示 首先将图片按照适当的比例显示在自定义控件中(当图片的宽度或者高度大于控件的宽度或者高度的时候,会对图片进行适当的缩放,当图 ...

  8. Iwfu-安卓Gesture手势(2)-实现多点触控控制图片的放大缩小。

    上一篇介绍安卓Gesture手势初步使用,这一篇用Gesture来实现多点触控达到控制图片放大缩小. 上文中写道,进行手势监听的Activity要实现对应的OnGestureListener接口,重写 ...

  9. windows8 开发教程 教你制作 多点触控Helper可将任意容器内任意对象进行多点缩放...

    实现方法: 对Manipulation进行抽象化 使不同容器可共用多点缩放事件, C# 代码如下: using System; using System.Collections.Generic; us ...

最新文章

  1. Python3 命名规范
  2. 每个人都有迷茫的时候,不知道接下来人生该怎么走?
  3. 树莓派 4G模块 PPP 拨号 NDIS 拨号
  4. java 读取excel 文件 Unable to recognize OLE stream 错误
  5. android开发复制文本,如何在Android应用中以编程方式复制文本?
  6. Win10上的dll依赖查看工具Dependencies
  7. 自己做量化交易软件(30)小白量化实战4--动于阴末止于阳极
  8. impalahive大数据平台数据血缘与数据地图(四)-impala血缘架构图及功能介绍
  9. matlab求多元函数微积分,基于Matlab软件求解多元函数积分
  10. 个人网站 域名 购买 解析 备案
  11. idea报Establishing SSL connection without server‘s identity verification is not recommended.
  12. maya2018模型传递点序
  13. 蓝牙BQB认证所需资料和流程
  14. B站才是头条系的大敌
  15. 解决vscode突然不能自动补全html标签
  16. imp导入时 出现IMP-00017:由于 ORACLE 错误 6550, 以下语句失败: 解决方法
  17. 抓包工具fiddler使用与理论的理解
  18. 微信传文件又慢又限制大小?试试这3个免费在线传文件工具!
  19. 数据库管理系统实验答案
  20. matlab中受控电流源怎么用,如何使用simulink进行光伏并网受控电流源的等效思路进行建模...

热门文章

  1. JS 制作简易计算机
  2. sofasofa——交通事故理赔审核
  3. 易语言多线程批量登录实现一键选中取消选择框账号
  4. 与卿共赴鸿蒙是什么意思,诉说爱情的诗句
  5. android webview加载html图片自适应手机屏幕大小点击查看大图
  6. U盘中毒或误操作变成raw格式,磁盘显示容量为0修复解决方案
  7. PPT训练营-时间轴
  8. wamp集成环境安装后无法启动的问题
  9. 系统重装 如何在PC上安装Mac OS,苹果操作系统
  10. html怎么能调用微信语音,华为微信的语音通话怎么录音