zoukankan      html  css  js  c++  java
  • 在viewPager中双指缩放图片,双击缩放图片,单指拖拽图片

     我们就把这个问题叫做图片查看器吧,它的主要功能有:

    (项目地址:https://github.com/TZHANHONG/ImageViewer/releases/tag/1.0,里面的MyImageView压缩包便是)

    1、双击缩放图片。

    2、 双指缩放图片。

    3、单指拖拽图片。

    为此这个图片查看器需要考虑以下的技术点:

    一、双击缩放图片:

    1、如果图片高度比屏幕的高度小得多,那么就将图片放大到高度与屏幕高度相等,否则就放大一个特定的倍数。

    2、如何判断是否到达这个倍数来停止缩放。

    3、判断完且停止放大后,图片可能已经超出了这个倍数需要的大小,如何回归到我们的目标大小。

    4、判断完且停止缩小后,图片宽度可能已经小于屏幕宽度,在两边留下了空白,如何重置为原来的大小。

    二、双指缩放图片:

    1、双指缩放,放大一个特定的倍数停止。

    2、如何判断是否到达这个倍数。

    3、放大停止后,图片可能已经超出了这个倍数需要的大小,如何回归到我们的目标大小。

    4、缩小停止后,图片宽度可能已经小于屏幕宽度,在两边留下了空白,如何重置为原来的大小。

    三、单指拖拽:

    1、当图片宽度小于或等于屏幕宽度的时候,禁止左右移动,当图片的高度小于屏幕高度的时候,禁止上下移动。

    2、移动图片时,如果图片的一边已经与屏幕之间有了空白,松手后恢复,让图片的这一边与屏幕边界重合。

    四、

    如何判断是双击,还是多指触控,还是单指。

    五、

    如何解决与viewPager的滑动冲突,当图片已经滑动到尽头无法滑动时,此时viewPager应该拦截事件。

    我们逐一来解决:

    public class MyImageView extends ImageView implements ViewTreeObserver.OnGlobalLayoutListener,View.OnTouchListener {
     public MyImageView(Context context, AttributeSet attrs) {
            super(context, attrs);
            super.setScaleType(ScaleType.MATRIX);
            setOnTouchListener(this);
            /**
             * 双击实现图片放大缩小
             */
            mGestureDetector = new GestureDetector(context,
                    new GestureDetector.SimpleOnGestureListener() {
                        @Override
                        public boolean onDoubleTap(MotionEvent e) {
                            changeViewSize(e);
                            return true;
                        }
                    });
        }

    在这里缩放图片是用matrix,因此首先要设置scaleType为matrix。
    用手势判断双击行为。不要忘了在onTouch里面加上

    if (mGestureDetector.onTouchEvent(event))
                return true;


    判断单指与多指触控,则在onTouch里面判断,要用 event.getAction() & MotionEvent.ACTION_MASK来判断。

    //多指触控模式,单指,双指
    private int mode;
    
    private final static int SINGLE_TOUCH = 1; //单指
    private final static int DOUBLE_TOUCH = 2; //双指
    @Override
        public boolean onTouch(View view, MotionEvent event) {
            rectF = getMatrixRectF();
            if (mGestureDetector.onTouchEvent(event))
                return true;
    
            switch (event.getAction() & event.getActionMasked()) {
                case MotionEvent.ACTION_DOWN:
                    mode = SINGLE_TOUCH;
                    break;
                case MotionEvent.ACTION_MOVE:
                    if (mode >= DOUBLE_TOUCH) //双指缩放
                    {
                    }
                    if (mode == SINGLE_TOUCH) //单指拖拽
                    {
                    }
                    break;
                case MotionEvent.ACTION_POINTER_DOWN:
                    mode += 1;break;
                case MotionEvent.ACTION_POINTER_UP:
                    mode -= 1;
                    break;
                case MotionEvent.ACTION_UP:
                    mode = 0;
                    break;
                //在ACTION_MOVE中,事件被拦截了之后,有时候ACTION_UP无法触发,所以加上了ACTION_CANCEL
                case MotionEvent.ACTION_CANCEL:
                    mode = 0;
                    break;
                default:
                    break;
            }
            return true;
        }

    有如下事件使我们要用到的:

    • MotionEvent.ACTION_DOWN:在第一个点被按下时触发
    • MotionEvent.ACTION_UP:当屏幕上唯一的点被放开时触发
    • MotionEvent.ACTION_POINTER_DOWN:当屏幕上已经有一个点被按住,此时再按下其他点时触发。
    • MotionEvent.ACTION_POINTER_UP:当屏幕上有多个点被按住,松开其中一个点时触发(即非最后一个点被放开时)。
    • MotionEvent.ACTION_MOVE:当有点在屏幕上移动时触发。值得注意的是,由于它的灵敏度很高,而我们的手指又不可能完全静止(即使我们感觉不到移动,但其实我们的手指也在不停地抖动),所以实际的情况是,基本上只要有点在屏幕上,此事件就会一直不停地被触发。

    在ACTION_MOVE中通过mode的大小来判断是单指还是双指。 

    不过有一个令人伤心的事情,Android自己有一个bug。经过测试发现双指交换触碰图片的时候,程序会闪退,出现异常:pointIndex out of range。这是Android自己的bug。个人觉得最好得解决方法是自定义一个viewPager,然后在里面重写:onTouchEvent,onInterceptTouchEvent,然后捕获异常。

       @Override
        public boolean onTouchEvent(MotionEvent ev) {
            try {
                return super.onTouchEvent(ev);
            } catch (IllegalArgumentException ex) {
                ex.printStackTrace();
            }
            return false;
        }
    
        @Override
        public boolean onInterceptTouchEvent(MotionEvent ev) {
            try {
                return super.onInterceptTouchEvent(ev);
            } catch (IllegalArgumentException ex) {
                ex.printStackTrace();
            }
            return false;
        }

    这样程序就不会闪退了。

    我们来看看双击放大的的代码:

        /**
         * 双击缩放图片
         */
        private void changeViewSize(MotionEvent e) {
            
            //获取双击的坐标
            final float x = e.getX();
            final float y = e.getY();
    
            //如果此时还在缩放那就直接返回
            if (animator != null && animator.isRunning())
                return;
            
            //判断是处于放大还是缩小的状态
            if (!isZoomChanged()) {
                animator = ValueAnimator.ofFloat(1.0f, 2.0f);
            } else {
                animator = ValueAnimator.ofFloat(1.0f, 0.0f);
            }
            animator.setTarget(this);
            animator.setDuration(500);
            animator.setInterpolator(new DecelerateInterpolator());
            animator.start();
            animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator valueAnimator) {
    
                    Float value = (Float) animator.getAnimatedValue();
                    matrix.postScale(value, value, x, y);
                    checkBorderAndCenterWhenScale(); //在缩放后让图片居中
                    setImageMatrix(matrix);
    
                    /**
                     * 控制缩小的范围
                     * 如果已经小于初始大小,那么恢复到初始大小,然后停止
                     */
                    if (checkRestScale()) {
                        matrix.set(oldMatrix);  //oldMatrix为最原始的matrix
                        setImageMatrix(matrix);
                        return;
                    }
                    /**
                     * 控制放大的范围
                     * 如果已经大于目标的放大倍数,那么直接置为目标的放大倍数
                     * 然后停止
                     */
                    if (getMatrixValueX() >= mDoubleClickScale)
                    {
                        matrix.postScale(mDoubleClickScale/getMatrixValueX(), mDoubleClickScale/getMatrixValueX(), x, y);
                        checkBorderAndCenterWhenScale();
                        setImageMatrix(matrix);
                        return;
                    }
                }
            });
        }

    判断处于放大还是缩小状态的代码:(不是初始值就说明是处于放大状态)

        /**
         * 判断缩放级别是否是改变过
         *
         * @return true表示非初始值, false表示初始值
         */
        private boolean isZoomChanged() {
            float[] values = new float[9];
            getImageMatrix().getValues(values);
            //获取当前X轴缩放级别
            float scale = values[Matrix.MSCALE_X];
            //获取初始时候的X轴缩放级别,两者做比较
            oldMatrix.getValues(values);
            return scale != values[Matrix.MSCALE_X];
        }

    getMatrixValue()的代码如下,是为了取得当前的放大倍数,相对于一开始的图片来说

     private float getMatrixValueX()
        {
            // TODO Auto-generated method stub
            float[] values = new float[9];
            getImageMatrix().getValues(values);
            //获取当前X轴缩放级别
            float scale = values[Matrix.MSCALE_X];
            //获取原始Matrix的X轴缩放级别
            oldMatrix.getValues(values);
    //返回放大的倍数
    return scale / values[Matrix.MSCALE_X]; }

    checkRestScale()的代码如下,主要是为了判断当前的缩放级别是否小于最初始的缩放级别。

        /**
         * 判断是否需要重置
         *
         * @return 当前缩放级别小于原始缩放级别时,重置
         */
        private boolean checkRestScale() {
            // TODO Auto-generated method stub
            float[] values = new float[9];
            getImageMatrix().getValues(values);
            //获取当前X轴缩放级别
            float scale = values[Matrix.MSCALE_X];
            //获取原始的X轴缩放级别,两者做比较
            oldMatrix.getValues(values);
            return scale < values[Matrix.MSCALE_X];
        }

    checkBorderAndCenterWhenScale()的代码如下,否则图片缩放后位置会发生变化。

    /**
         * 在缩放时,进行图片显示范围的控制
         */
        private void checkBorderAndCenterWhenScale()
        {
    
            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();
            }
    
            matrix.postTranslate(deltaX, deltaY);
            setImageMatrix(matrix);
        }

     

    接下来看看双指缩放和单指拖拽:

    @Override
        public boolean onTouch(View view, MotionEvent event) {
            rectF = getMatrixRectF(); //获取图片边界范围
            if (mGestureDetector.onTouchEvent(event))
                return true;
    
            switch (event.getAction() & event.getActionMasked()) {
                case MotionEvent.ACTION_DOWN:
                    //如果放大后图片的边界超出了屏幕,那么就拦截事件,不让viewPager处理
                    if (rectF.width() > getWidth() || rectF.height() > getHeight()) {
                        getParent().requestDisallowInterceptTouchEvent(true);
                    }
                    mode = SINGLE_TOUCH;
    
                    x = (int) event.getRawX();
                    y = (int) event.getRawY();
                    break;
                case MotionEvent.ACTION_MOVE:
                    if (mode >= DOUBLE_TOUCH) //双指缩放
                    {
                        getParent().requestDisallowInterceptTouchEvent(true);
                        newDist = calculateDist(event); //计算距离
                        Point point = getMiPoint(event); //获取两手指间的中点坐标
                        if (newDist > oldDist + 1) //放大(加一是为了防止抖动)
                        {
                            changeViewSize(oldDist, newDist, point); //根据距离实现放大缩小
                            oldDist = newDist;
                        }
                        if (oldDist > newDist + 1) //缩小
                        {
                            changeViewSize(oldDist, newDist, point);
                            oldDist = newDist;
                        }
                    }
                    if (mode == SINGLE_TOUCH) //单指拖拽
                    {
                        float dx = event.getRawX() - x;
                        float dy = event.getRawY() - y;
    
                        //如果移动过程中图片的边界超出了屏幕,那么就拦截事件,不让viewPager处理
                        if (rectF.width() > getWidth() || rectF.height() > getHeight()) {
                            getParent().requestDisallowInterceptTouchEvent(true);
                        }
                        //如果向右移动图片到了尽头,那么就不要拦截事件,让viewPager处理
                        if (rectF.left >= 0 && dx > 0)
                            getParent().requestDisallowInterceptTouchEvent(false);
    
                        //如果向左移动到了尽头,那么就不要拦截事件,让viewPager处理
                        if (rectF.right <= getWidth() && dx < 0)
                            getParent().requestDisallowInterceptTouchEvent(false);
    
                        if (getDrawable() != null) {
                            //如果图片宽度或高度没有超出屏幕,那么就禁止左右或上下滑动
                            if (rectF.width() <= getWidth())
                                dx = 0;
                            if (rectF.height() < getHeight())
                                dy = 0;
    
                            //如果图片向下移动到了尽头,不让它继续移动
                            if (rectF.top >= 0 && dy > 0)
                                dy = 0;
                            //如果图片向上移动到了尽头,不让它继续移动
                            if (rectF.bottom <= getHeight() && dy < 0)
                                dy = 0;
    
                            //当移动距离大于1的时候再移动,因为ACTION_MOVE比较灵敏,
                            // 手指即使只是放在上面,依然能够检测到手指的抖动,然后让图片移动。
                            if (Math.abs(dx) > 1 || Math.abs(dy) > 1)
                                matrix.postTranslate(dx, dy);
                            setImageMatrix(matrix);
                        }
                    }
                    x = (int) event.getRawX();
                    y = (int) event.getRawY();
                    break;
                case MotionEvent.ACTION_POINTER_DOWN:
                    mode += 1;
                    oldDist = calculateDist(event); break;
                case MotionEvent.ACTION_POINTER_UP:
                    mode -= 1;
                    break;
                case MotionEvent.ACTION_UP:
                    backToPosition();
                    mode = 0;
                    break;
                //在ACTION_MOVE中,事件被拦截了之后,有时候ACTION_UP无法触发,所以加上了ACTION_CANCEL
                case MotionEvent.ACTION_CANCEL:
                    backToPosition();
                    mode = 0;
                    break;
                default:
                    break;
            }
            return true;
        }

    首先先来看一个方法,根据图片的matrix获得图片的边界范围,这个范围映射在rect上。(这个范围检测是用在单指拖拽上的)

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

    rect.bottom:图片下边界的纵坐标

    rect.left:图片左边界的横坐标

    rect.right:图片右边界的横坐标

    rect.top:图片上边界的纵坐标

    rectF.width():图片宽度

    rectF.height():图片高度 

    需要注意的是Matrix对图片的操作都是操作ImageView里面的bitmap,ImageView是没有变化的,上面所说的屏幕边界其实ImageView的边界,getWidth(),getHeight()是ImageView的宽和高。

    方法 backToPosition()主要是实现单指拖拽的技术点2,当手指快速划过去的时候,在检测到无法继续滑动前图片边界与屏幕边界已经出现了距离,所以松开手指的时候要复位,让图片边界与屏幕边界重合。

    /**
         * 若是在移动后图片的边界脱离屏幕边界,那么就让图片边界与屏幕边界重合
         * 若手指快速移动,停止后会出现图片距离屏幕有一段空白距离,然后经过判断不能再移动,
         * 但是在进行下一次判断是否可以继续移动之前就已经出现了。
         * 所以需要复位
         */
        private void backToPosition() {
            if (rectF.left >= 0) { //图片左边界与屏幕出现距离
                matrix.postTranslate(-rectF.left, 0);
                setImageMatrix(matrix);
            }
            if (rectF.right <= getWidth()) { //图片右边界与屏幕出现距离
                matrix.postTranslate(getWidth() - rectF.right, 0);
                setImageMatrix(matrix);
            }
            if (rectF.top >= 0) { //图片上边界与屏幕出现距离
                matrix.postTranslate(0, -rectF.top);
                setImageMatrix(matrix);
            }
            if (rectF.bottom <= getHeight()) { //图片下边界与屏幕出现距离
                matrix.postTranslate(0, getHeight() - rectF.bottom);
                setImageMatrix(matrix);
            }
        }

    获取两手指间的中点坐标

        /**
         * 获取双指缩放时候的缩放中点
         *
         * @return
         */
        private Point getMiPoint(MotionEvent event) {
    
            float x = event.getX(0) + event.getX(1);
            float y = event.getY(0) + event.getY(1);
    
            mPoint.set((int) x / 2, (int) y / 2);
            return mPoint;
        }

    计算两指触摸点的距离

        /**
         * 计算两指触摸点之间的距离
         */
        private float calculateDist(MotionEvent event) {
    
            float x = event.getX(0) - event.getX(1);
            float y = event.getY(0) - event.getY(1);
            return (float) Math.sqrt(x * x + y * y);
    
        }

    双指缩放图片

        /**
         * 双指缩放图片
         */
        private void changeViewSize(float oldDist, float newDist, Point mPoint) {
            float scale = newDist / oldDist; //缩放比例
    
            matrix.postScale(scale, scale, mPoint.x, mPoint.y);
            checkBorderAndCenterWhenScale();
            setImageMatrix(matrix);
            
            //防止缩小的时候小于初始的图片大小,需要重置
            reSetMatrix();
            //如果缩放已经大于目标倍数,停止,因为有可能已经超出,那么就直接缩放到目标大小
            if (getMatrixValueX() >= MAX_SCALE) 
            {
                matrix.postScale(MAX_SCALE/getMatrixValueX(), MAX_SCALE/getMatrixValueX(), x, y);
                checkBorderAndCenterWhenScale();
                setImageMatrix(matrix);
                return;
            }
        }

    reSetMatrix()的代码如下:

        /**
         * 重置Matrix
         */
        private void reSetMatrix() {
            if (checkRestScale()) {
                matrix.set(oldMatrix);
                setImageMatrix(matrix);
                return;
            }
        }

    checkRestScale()的代码在上面已经给出了。oldMatrix为最初始的Matrix。

    到这里还没有结束,设置Imageview的ScaleType为Matrix,那么图片不会主动缩放到适应屏幕,也不会处于屏幕中间,因此我们的自定义ImageView需要继承ViewTreeObserver.OnGlobalLayoutListener

     @Override
        protected void onAttachedToWindow() {
            super.onAttachedToWindow();
            getViewTreeObserver().addOnGlobalLayoutListener(this);
        }
     @Override
        public void onGlobalLayout() {
            if (once)
            {
                Drawable d = getDrawable();
                if (d == null)
                    return;
                Log.e("TAG", d.getIntrinsicWidth() + " , " + d.getIntrinsicHeight());
                int width = getWidth();
                int height = getHeight();
                // 拿到图片的宽和高
                int dw = d.getIntrinsicWidth();
                int dh = d.getIntrinsicHeight();
                float scale = 1.0f;
                // 如果图片的宽或者高大于屏幕,则缩放至屏幕的宽或者高
                if (dw > width && dh <= height)
                {
                    scale = width * 1.0f / dw;
                }
                if (dh > height && dw <= width)
                {
                    scale = height * 1.0f / dh;
                }
                // 如果宽和高都大于屏幕,则让其按按比例适应屏幕大小
                if (dw > width && dh > height)
                {
                    scale = Math.min(width * 1.0f / dw, height * 1.0f / dh);
                }
                initScale = scale;
    
                Log.e("TAG", "initScale = " + initScale);
                matrix.postTranslate((width - dw) / 2, (height - dh) / 2);
                matrix.postScale(scale, scale, getWidth() / 2,
                        getHeight() / 2);
                // 图片移动至屏幕中心
                setImageMatrix(matrix);
    
                oldMatrix.set(getImageMatrix());
                once = false;
    
                RectF rectF=getMatrixRectF();
                setDoubleClickScale(rectF);
    
            }
        }

     // 拿到图片的宽和高
     int dw = d.getIntrinsicWidth();
     int dh = d.getIntrinsicHeight();
    拿到的图片宽和高是bitmap的真实高度。

    初始的oldMatrix就是在这里设置的,然后作为初始模板,代表着图片没被动手改变的Matrix。至于方法 setDoubleClickScale(rectF);只是设置双击放大的倍数而已,如果图片高度比屏幕的高度小得多,那么就将图片放大到高度与屏幕高度相等,否则就放大一个特定的倍数。必须在这里设置,因为在这里取到的rectF才能反映原始图片的边界,因为这时候还没有动手改变图片。

        /**
         * 设置双击放大的倍数
         */
        private void setDoubleClickScale(RectF rectF)
        {
            if(rectF.height()<getHeight()-100)
            {
                mDoubleClickScale=getHeight()/rectF.height();
            }
            else
                mDoubleClickScale=2f;
        }

    到这里大概结束了,下面就贴出完整的代码:

    package com.example.tangzh.myimageview;
    
    import android.animation.ValueAnimator;
    import android.content.Context;
    import android.graphics.Matrix;
    import android.graphics.Point;
    import android.graphics.RectF;
    import android.graphics.drawable.Drawable;
    import android.util.AttributeSet;
    import android.util.Log;
    import android.view.GestureDetector;
    import android.view.MotionEvent;
    import android.view.View;
    import android.view.ViewTreeObserver;
    import android.view.animation.DecelerateInterpolator;
    import android.widget.ImageView;
    
    /**
     * Created by TangZH on 2017/5/3.
     */
    public class MyImageView extends ImageView implements ViewTreeObserver.OnGlobalLayoutListener,View.OnTouchListener {
        private final static int SINGLE_TOUCH = 1; //单指
        private final static int DOUBLE_TOUCH = 2; //双指
    
        //多指触控模式,单指,双指
        private int mode;
    
        //两指触碰点之间的距离
        private float oldDist;
        private float newDist;
    
        /**
         * 最大缩放级别
         */
        private static final float MAX_SCALE = 5f;
        /**
         * 双击时的缩放级别
         */
        private  float mDoubleClickScale = 2;
    
    
        /**
         * 初始化时的缩放比例,如果图片宽或高大于屏幕,此值将小于0
         */
        private float initScale = 1.0f;
        private boolean once = true;
        private RectF rectF;
    
        /**
         * 用于双击检测
         */
        private GestureDetector mGestureDetector;
        private int x = 0;
        private int y = 0;
    
        private Point mPoint = new Point();
    
        private final Matrix matrix = new Matrix();
        private Matrix oldMatrix = new Matrix();
    
        private ValueAnimator animator;
    
        public MyImageView(Context context) {
            this(context, null);
        }
    
        public MyImageView(Context context, AttributeSet attrs) {
            super(context, attrs);
            super.setScaleType(ScaleType.MATRIX);
            setOnTouchListener(this);
            /**
             * 双击实现图片放大缩小
             */
            mGestureDetector = new GestureDetector(context,
                    new GestureDetector.SimpleOnGestureListener() {
                        @Override
                        public boolean onDoubleTap(MotionEvent e) {
                            changeViewSize(e);
                            return true;
                        }
                    });
        }
    
        @Override
        public boolean onTouch(View view, MotionEvent event) {
            rectF = getMatrixRectF(); //获取图片边界范围
            if (mGestureDetector.onTouchEvent(event))
                return true;
    
            switch (event.getAction() & event.getActionMasked()) {
                case MotionEvent.ACTION_DOWN:
                    //如果放大后图片的边界超出了屏幕,那么就拦截事件,不让viewPager处理
                    if (rectF.width() > getWidth() || rectF.height() > getHeight()) {
                        getParent().requestDisallowInterceptTouchEvent(true);
                    }
                    mode = SINGLE_TOUCH;
    
                    x = (int) event.getRawX();
                    y = (int) event.getRawY();
                    break;
                case MotionEvent.ACTION_MOVE:
                    if (mode >= DOUBLE_TOUCH) //双指缩放
                    {
                        getParent().requestDisallowInterceptTouchEvent(true);
                        newDist = calculateDist(event); //计算距离
                        Point point = getMiPoint(event); //获取两手指间的中点坐标
                        if (newDist > oldDist + 1) //放大(加一是为了防止抖动)
                        {
                            changeViewSize(oldDist, newDist, point); //根据距离实现放大缩小
                            oldDist = newDist;
                        }
                        if (oldDist > newDist + 1) //缩小
                        {
                            changeViewSize(oldDist, newDist, point);
                            oldDist = newDist;
                        }
                    }
                    if (mode == SINGLE_TOUCH) //单指拖拽
                    {
                        float dx = event.getRawX() - x;
                        float dy = event.getRawY() - y;
    
                        //如果移动过程中图片的边界超出了屏幕,那么就拦截事件,不让viewPager处理
                        if (rectF.width() > getWidth() || rectF.height() > getHeight()) {
                            getParent().requestDisallowInterceptTouchEvent(true);
                        }
                        //如果向右移动图片到了尽头,那么就不要拦截事件,让viewPager处理
                        if (rectF.left >= 0 && dx > 0)
                            getParent().requestDisallowInterceptTouchEvent(false);
    
                        //如果向左移动到了尽头,那么就不要拦截事件,让viewPager处理
                        if (rectF.right <= getWidth() && dx < 0)
                            getParent().requestDisallowInterceptTouchEvent(false);
    
                        if (getDrawable() != null) {
                            //如果图片宽度或高度没有超出屏幕,那么就禁止左右或上下滑动
                            if (rectF.width() <= getWidth())
                                dx = 0;
                            if (rectF.height() < getHeight())
                                dy = 0;
    
                            //如果图片向下移动到了尽头,不让它继续移动
                            if (rectF.top >= 0 && dy > 0)
                                dy = 0;
                            //如果图片向上移动到了尽头,不让它继续移动
                            if (rectF.bottom <= getHeight() && dy < 0)
                                dy = 0;
    
                            //当移动距离大于1的时候再移动,因为ACTION_MOVE比较灵敏,
                            // 手指即使只是放在上面,依然能够检测到手指的抖动,然后让图片移动。
                            if (Math.abs(dx) > 1 || Math.abs(dy) > 1)
                                matrix.postTranslate(dx, dy);
                            setImageMatrix(matrix);
                        }
                    }
                    x = (int) event.getRawX();
                    y = (int) event.getRawY();
                    break;
                case MotionEvent.ACTION_POINTER_DOWN:
                    mode += 1;
                    oldDist = calculateDist(event);
                    Log.e("q", "" + "a");
    
                    Log.e(":::", "" + event.getPointerCount() + "   " + event.getActionIndex() + "   " + event.findPointerIndex(0));
                    break;
                case MotionEvent.ACTION_POINTER_UP:
                    mode -= 1;
                    break;
                case MotionEvent.ACTION_UP:
                    backToPosition();
                    mode = 0;
                    break;
                //在ACTION_MOVE中,事件被拦截了之后,有时候ACTION_UP无法触发,所以加上了ACTION_CANCEL
                case MotionEvent.ACTION_CANCEL:
                    backToPosition();
                    mode = 0;
                    break;
                default:
                    break;
            }
            return true;
        }
    
        /**
         * 计算两指触摸点之间的距离
         */
        private float calculateDist(MotionEvent event) {
    
            float x = event.getX(0) - event.getX(1);
            float y = event.getY(0) - event.getY(1);
            return (float) Math.sqrt(x * x + y * y);
    
        }
    
        @Override
        protected void onAttachedToWindow() {
            super.onAttachedToWindow();
            getViewTreeObserver().addOnGlobalLayoutListener(this);
        }
    
        /**
         * 若是在移动后图片的边界脱离屏幕边界,那么就让图片边界与屏幕边界重合
         * 若手指快速移动,停止后会出现图片距离屏幕有一段空白距离,然后经过判断不能再移动,
         * 但是在进行下一次判断是否可以继续移动之前就已经出现了。
         * 所以需要复位
         */
        private void backToPosition() {
            if (rectF.left >= 0) { //图片左边界与屏幕出现距离
                matrix.postTranslate(-rectF.left, 0);
                setImageMatrix(matrix);
            }
            if (rectF.right <= getWidth()) { //图片右边界与屏幕出现距离
                matrix.postTranslate(getWidth() - rectF.right, 0);
                setImageMatrix(matrix);
            }
            if (rectF.top >= 0) { //图片上边界与屏幕出现距离
                matrix.postTranslate(0, -rectF.top);
                setImageMatrix(matrix);
            }
            if (rectF.bottom <= getHeight()) { //图片下边界与屏幕出现距离
                matrix.postTranslate(0, getHeight() - rectF.bottom);
                setImageMatrix(matrix);
            }
        }
    
    
        /**
         * 获取双指缩放时候的缩放中点
         *
         * @return
         */
        private Point getMiPoint(MotionEvent event) {
    
            float x = event.getX(0) + event.getX(1);
            float y = event.getY(0) + event.getY(1);
    
            mPoint.set((int) x / 2, (int) y / 2);
            return mPoint;
        }
    
        /**
         * 双指缩放图片
         */
        private void changeViewSize(float oldDist, float newDist, Point mPoint) {
            float scale = newDist / oldDist; //缩放比例
    
            matrix.postScale(scale, scale, mPoint.x, mPoint.y);
            checkBorderAndCenterWhenScale();
            setImageMatrix(matrix);
    
            //防止缩小的时候小于初始的图片大小,需要重置
            reSetMatrix();
            //如果缩放已经大于目标倍数,停止,因为有可能已经超出,那么就直接缩放到目标大小
            if (getMatrixValueX() >= MAX_SCALE)
            {
                matrix.postScale(MAX_SCALE/getMatrixValueX(), MAX_SCALE/getMatrixValueX(), x, y);
                checkBorderAndCenterWhenScale();
                setImageMatrix(matrix);
                return;
            }
        }
    
        /**
         * 双击缩放图片
         */
        private void changeViewSize(MotionEvent e) {
    
            //获取双击的坐标
            final float x = e.getX();
            final float y = e.getY();
    
            //如果此时还在缩放那就直接返回
            if (animator != null && animator.isRunning())
                return;
    
            //判断是处于放大还是缩小的状态
            if (!isZoomChanged()) {
                animator = ValueAnimator.ofFloat(1.0f, 2.0f);
            } else {
                animator = ValueAnimator.ofFloat(1.0f, 0.0f);
            }
            animator.setTarget(this);
            animator.setDuration(500);
            animator.setInterpolator(new DecelerateInterpolator());
            animator.start();
            animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator valueAnimator) {
    
                    Float value = (Float) animator.getAnimatedValue();
                    matrix.postScale(value, value, x, y);
                    checkBorderAndCenterWhenScale();
                    setImageMatrix(matrix);
    
                    /**
                     * 控制缩小的范围
                     * 如果已经小于初始大小,那么恢复到初始大小,然后停止
                     */
                    if (checkRestScale()) {
                        matrix.set(oldMatrix);
                        setImageMatrix(matrix);
                        return;
                    }
                    /**
                     * 控制放大的范围
                     * 如果已经大于目标的放大倍数,那么直接置为目标的放大倍数
                     * 然后停止
                     */
                    if (getMatrixValueX() >= mDoubleClickScale)
                    {
                        matrix.postScale(mDoubleClickScale/getMatrixValueX(), mDoubleClickScale/getMatrixValueX(), x, y);
                        checkBorderAndCenterWhenScale();
                        setImageMatrix(matrix);
                        return;
                    }
                }
            });
        }
    
        /**
         * 判断缩放级别是否是改变过
         *
         * @return true表示非初始值, false表示初始值
         */
        private boolean isZoomChanged() {
            float[] values = new float[9];
            getImageMatrix().getValues(values);
            //获取当前X轴缩放级别
            float scale = values[Matrix.MSCALE_X];
            //获取模板的X轴缩放级别,两者做比较
            oldMatrix.getValues(values);
            return scale != values[Matrix.MSCALE_X];
        }
    
        /**
         * 重置Matrix
         */
        private void reSetMatrix() {
            if (checkRestScale()) {
                matrix.set(oldMatrix);
                setImageMatrix(matrix);
                return;
            }
        }
    
        /**
         * 设置双击放大的倍数
         */
        private void setDoubleClickScale(RectF rectF)
        {
            if(rectF.height()<getHeight()-100)
            {
                mDoubleClickScale=getHeight()/rectF.height();
            }
            else
                mDoubleClickScale=2f;
        }
    
        /**
         * 判断是否需要重置
         *
         * @return 当前缩放级别小于模板缩放级别时,重置
         */
        private boolean checkRestScale() {
            // TODO Auto-generated method stub
            float[] values = new float[9];
            getImageMatrix().getValues(values);
            //获取当前X轴缩放级别
            float scale = values[Matrix.MSCALE_X];
            //获取模板的X轴缩放级别,两者做比较
            oldMatrix.getValues(values);
            return scale < values[Matrix.MSCALE_X];
        }
    
        private float getMatrixValueX()
        {
            // TODO Auto-generated method stub
            float[] values = new float[9];
            getImageMatrix().getValues(values);
            //获取当前X轴缩放级别
            float scale = values[Matrix.MSCALE_X];
            //获取模板的X轴缩放级别,两者做比较
            oldMatrix.getValues(values);
            return scale / values[Matrix.MSCALE_X];
        }
        /**
         * 在缩放时,进行图片显示范围的控制
         */
        private void checkBorderAndCenterWhenScale()
        {
    
            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();
            }
            Log.e("TAG", "deltaX = " + deltaX + " , deltaY = " + deltaY);
    
            matrix.postTranslate(deltaX, deltaY);
            setImageMatrix(matrix);
        }
        /**
         * 根据当前图片的Matrix获得图片的范围
         *
         * @return
         */
        private RectF getMatrixRectF()
        {
            RectF rect = new RectF();
            Drawable d = getDrawable();
            if (null != d)
            {
                rect.set(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());
                matrix.mapRect(rect); //如果没有这个,那么下面Log的输出将会与上一句的一样。
            }
            Log.e("aaaa",""+rect.bottom+"  "+rect.left+"   "+rect.right+"  "+rect.top);
            return rect;
        }
    
        @Override
        public void onGlobalLayout() {
            if (once)
            {
                Drawable d = getDrawable();
                if (d == null)
                    return;
                Log.e("TAG", d.getIntrinsicWidth() + " , " + d.getIntrinsicHeight());
                int width = getWidth();
                int height = getHeight();
                // 拿到图片的宽和高
                int dw = d.getIntrinsicWidth();
                int dh = d.getIntrinsicHeight();
                float scale = 1.0f;
                // 如果图片的宽或者高大于屏幕,则缩放至屏幕的宽或者高
                if (dw > width && dh <= height)
                {
                    scale = width * 1.0f / dw;
                }
                if (dh > height && dw <= width)
                {
                    scale = height * 1.0f / dh;
                }
                // 如果宽和高都大于屏幕,则让其按按比例适应屏幕大小
                if (dw > width && dh > height)
                {
                    scale = Math.min(width * 1.0f / dw, height * 1.0f / dh);
                }
                initScale = scale;
    
                Log.e("TAG", "initScale = " + initScale);
                matrix.postTranslate((width - dw) / 2, (height - dh) / 2);
                matrix.postScale(scale, scale, getWidth() / 2,
                        getHeight() / 2);
                // 图片移动至屏幕中心
                setImageMatrix(matrix);
    
                oldMatrix.set(getImageMatrix());
                once = false;
    
                RectF rectF=getMatrixRectF();
                setDoubleClickScale(rectF);
    
            }
        }
    }

    唉,虽然已经写完了,但是还有一个问题没有解决,就是移动图片到尽头,这时候不要放手,往反方向移动,就会出现一个问题,图片反方向的部分被遮挡,无法看到,然后移动的时候是直接切换图片,而不再继续移动图片,这个问题的原因是:当你移动图片到尽头时,就把事件交给viewpager来处理了,即使再往反方向移动图片,viewPager也一样继续拦截了事件。目前没解决。

    参考博客:http://blog.csdn.net/lmj623565791/article/details/39480503

                 http://blog.csdn.net/nangongyanya/article/details/50697217

  • 相关阅读:
    使用C#调用Java带MIME附件WebService方法的初步设想
    到底是哪里给我插的广告?
    jQuery mobile X天教程 目录
    让ksoap支持wsse加密的soap报文
    第一天 认识jQuery mobile 框架,资源,书籍
    谈谈去那里找开源项目
    第二天 jQuery mobile 的Page&Dialogs,Toolbars,Button,listView全接触
    增强型MonkeyRunner 脚本计划
    以前我喜欢盗版,但是,我现在痛恨盗版
    android Handler 机制研究学习笔记
  • 原文地址:https://www.cnblogs.com/tangZH/p/6839343.html
Copyright © 2011-2022 走看看