android 图解 PhotoView,从‘百草园’到‘三味书屋’!

47 篇文章 14 订阅
14 篇文章 2 订阅

⚠️:考虑到部分java开发者不熟悉kt,本篇采用java语言来编写! 底部附kotlin/java版源码

先来看看今天的效果图:

横向图片纵向图片

需求:

  • 图片
    • 横向图片 默认左右靠边 上下留白
    • 总想图片 默认上下靠边 左右留白
  • 双击 放大/缩小,放大后可单指移动
  • 双指 放大
  • 最小缩小不能小于初始图片,最大方法不能大于图片的1.5倍

最基础,绘制一张图片!

public class PhotoView2 extends View {
	 // 需要操作的图片
    private Bitmap mBitMap;

    // 画笔
    Paint mPaint = new Paint();	

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

   public PhotoView2(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    @SuppressLint("CustomViewStyleable")
    public PhotoView2(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
		 TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.PhotoView);

        Drawable drawable = typedArray.getDrawable(R.styleable.PhotoView_android_src);
        if (drawable == null)
            mBitMap = BitmapFactory.decodeResource(context.getResources(), R.mipmap.error);
        else
            mBitMap = toBitMap(drawable, 800, 800);

        // 回收 避免内存泄漏
        typedArray.recycle();
	}
	
	 @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        // 绘制一张图片 其实位置为0,0
        canvas.drawBitmap(mBitMap, 0, 0, mPaint);
    }


	// drawable -> bitmap
	private Bitmap toBitMap(Drawable drawable, int width, int height) {
        Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(bitmap);
        drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
        drawable.draw(canvas);
        return bitmap;
    }
}

这部分代码比较简单,过一下就完事了!
在这里插入图片描述

图片居中

众所周知,在自定义View时候

View的执行流程为-> 构造方法 -> onMeasure() -> onSizeChanged() -> onDraw()

在绘制(onDraw)之前获取到偏移量即可

#PhotoView2.java

	 // 将图片移动到View中心
    float offsetWidth = 0f;
    float offsetHeight = 0f;
    
	@Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
 		offsetWidth = getWidth() / 2f - mBitMap.getWidth() / 2f;
        offsetHeight = getHeight() / 2f - mBitMap.getHeight() / 2f;
}

	 @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        // 参数一:图片
        // 参数二:图片x位置
        // 参数三:图片y的位置
        // 参数四:画笔
        canvas.drawBitmap(mBitMap, offsetWidth, offsetHeight, mPaint);
    }

看不懂没关系,来张图就一目了然了!
在这里插入图片描述

当前的效果


这块还是比较基础的东西!接下来要提高难度了…

图片放大

为了满足需求一,先将图片放大到合适的位置

需求一:

  • 图片
    - 横向图片 默认左右靠边 上下留白
    - 纵向图片 默认上下靠边 左右留白

需求一辅助图:

纵向图片横向图片

先来看代码:

#PhotoView2.java

// 缩放前图片比例
    float smallScale = 0f;
    
    // 缩放后图片
    float bigScale = 0f;

    // 当前比例
    float currentScale = 0f;

    // 缩放倍数
    private static final float ZOOM_SCALE = 1.5f;
    
 @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);

     	// view比例
        float viewScale = (float) getWidth() / (float) getHeight();
        
        // 图片比例
        float bitScale = (float) mBitMap.getWidth() / (float) mBitMap.getHeight();

        // 如果图片比例大于view比例
        if (bitScale > viewScale) {
            // 横向图片
            smallScale = (float) getWidth() / (float) mBitMap.getWidth();
            bigScale = (float) getHeight() / (float) mBitMap.getHeight() * ZOOM_SCALE;
        } else {
            // 纵向图片
            smallScale = (float) getHeight() / (float) mBitMap.getHeight();
            bigScale = (float) getWidth() / (float) mBitMap.getWidth() * ZOOM_SCALE;
        }

        // 当前缩放比例 = 缩放前的比例
        currentScale = smallScale;
    }

	 @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        /*
         *  参数一: x 缩放比例
         *  参数二: y 缩放比例
         *  参数三: x 轴位置
         *  参数四: y 轴位置
         *  
         *  这里为了简单起见,所以x,y缩放比例使用的同一个值[currentScale]
         */
        canvas.scale(currentScale, currentScale, getWidth() / 2f, getHeight() / 2f);

        canvas.drawBitmap(mBitMap, offsetWidth, offsetHeight, mPaint);
    }

smallScale/bigScale还不懂是什么? 以横向图片为例,带入参数,一张图搞懂!

在这里插入图片描述

  • smallScale 缩放原来图片的1.5倍
  • bigScale 缩放原来的2.4倍

以横向图片关键代码举例:

// 横向图片
smallScale = (float) getWidth() / (float) mBitMap.getWidth();
bigScale = (float) getHeight() / (float) mBitMap.getHeight() * ZOOM_SCALE;

如果是横向图片,那么证明 height > width

所以 smallScale 缩放比例就是width / bitmap.width ,让左右不留白,上下留白

bigScale 缩放比例这里采取height的比例 * 1.5 是为了防止图片过小从而没有超出整个屏幕

当前的效果


双击放大

提到双击放大,就不得不提到android中自带的监听双击的类

#PhotoView2.java

	// 双击手势监听
    static class PhotoGestureListener extends GestureDetector.SimpleOnGestureListener {
        // 单击情况 : 抬起[ACTION_UP]时候触发
        // 双击情况 : 第二次抬起[ACTION_POINTER_UP]时候触发
        @Override
        public boolean onSingleTapUp(MotionEvent e) {
            Log.i("szjPhotoGestureListener", "抬起了 onSingleTapUp");
            return super.onSingleTapUp(e);
        }

        // 长按时触发 [300ms]
        @Override
        public void onLongPress(MotionEvent e) {
            Log.i("szjPhotoGestureListener", "长按了 onLongPress");
            super.onLongPress(e);
        }

        // 滑动时候触发 类似 ACTION_MOVE 事件
        @Override
        public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
            Log.i("szjPhotoGestureListener", "滑动了  onScroll");
            return super.onScroll(e1, e2, distanceX, distanceY);
        }

        // 滑翔/飞翔 [惯性滑动]
        @Override
        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
            Log.i("szjPhotoGestureListener", "惯性滑动 onFling");
            return super.onFling(e1, e2, velocityX, velocityY);
        }

        // 延时触发 [100ms] -- 常用与水波纹等效果
        @Override
        public void onShowPress(MotionEvent e) {
            super.onShowPress(e);
            Log.i("szjPhotoGestureListener", "延时触发 onShowPress");
        }

        // 按下 这里必须返回true 因为所有事件都是由按下出发的
        @Override
        public boolean onDown(MotionEvent e) {
            return true;
        }

        // 双击 -- 第二次按下时候触发 (40ms - 300ms) [小于40ms是为了防止抖动]
        @Override
        public boolean onDoubleTap(MotionEvent e) {
            Log.i("szjPhotoGestureListener", "双击了 onDoubleTap");
            return super.onDoubleTap(e);
        }

        // 双击 第二次的事件处理 DOWN MOVE UP 都会执行到这里
        @Override
        public boolean onDoubleTapEvent(MotionEvent e) {
            Log.i("szjPhotoGestureListener", "双击执行了 onDoubleTapEvent");
            return super.onDoubleTapEvent(e);
        }

        // 单击时触发 双击时不触发
        @Override
        public boolean onSingleTapConfirmed(MotionEvent e) {
            Log.i("szjPhotoGestureListener", "单击了 onSingleTapConfirmed");
            return super.onSingleTapConfirmed(e);
        }
    }

这里我都打log写注释了,自己测测很容易拿捏,对于放大来说,最重要的当然是双击事件 onDoubleTap()

直接看代码

#PhotoGestureListener.java

    // 是否双击 [默认第一次点击是缩小]
    boolean isDoubleClick = false;
    
	    // 双击 -- 第二次按下时候触发 (40ms - 300ms) [小于40ms是为了防止抖动]
        @Override
        public boolean onDoubleTap(MotionEvent e) {
        	// 先改为放大,第一次点击是放大效果
            isDoubleClick = !isDoubleClick;
            if (isDoubleClick) {
                // 放大 放大到最大比例
                currentScale = bigScale;
            } else {
                // 缩小 缩小为左右留白的比例
                currentScale = smallScale;
            }
            // 刷新 onDraw
            invalidate();
          
            return super.onDoubleTap(e);
        }

记得初始化PhotoGestureListener

众所周知,单击事件(DOWN) / 触摸事件(MOVE) / 抬起事件(UP) 由onTouchEvent()可以监听到,那么作为双击事件,也是同样的道理!!

注意⚠️⚠️ :onDown() 必须返回true,因为DOWN事件是所有事件的起点

#PhotoView2.java

	// 双击操作
private final GestureDetector mPhotoGestureListener;

public PhotoView2(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
		... 构造方法中初始化 ...
		 mPhotoGestureListener = new GestureDetector(context, new PhotoGestureListener());
}

// 双击事件传递下去
@Override
public boolean onTouchEvent(MotionEvent event) {
    return mPhotoGestureListener.onTouchEvent(event);
}

当前的效果


双击放大添加动画

现在的效果还有点《粗糙》,接下来添加一个缩放的动画

#PhotoGestureListener.java

        @Override
        public boolean onDoubleTap(MotionEvent e) {
         isDoubleClick = !isDoubleClick;
            if (isDoubleClick) {
                // 放大
//                currentScale = bigScale;
                scaleAnimation(currentScale, bigScale).start();
            } else {
                // 缩小
//                currentScale = smallScale;
                scaleAnimation(bigScale, smallScale).start();
            }
            // 不需要刷新了,在属性动画调用setCurrentScale() 的时候已经刷新了
//            invalidate();
           
            return super.onDoubleTap(e);
        }

// 缩放动画
public ObjectAnimator scaleAnimation(float start, float end) {
        ObjectAnimator animator = ObjectAnimator.ofFloat(this, "currentScale", start, end);
        // 动画时间
        animator.setDuration(500);
        return animator;
    }

    // 属性动画的关键!!  内部通过反射调用set方法来赋值
    public void setCurrentScale(float currentScale) {
        this.currentScale = currentScale;
        invalidate();
    }

当前的效果

放大后图片滑动

这里为了代码规范,行,y坐标我就写成一个OffSet类了

data class OffSet(var x: Float, var y: Float)

还是在双击手势类里面,onScroll()类似ACTION_MOVE事件,所以监听这个也是一样的[PhotoGestureListener]

#PohtoView2.java

  // 放大后手指移动位置
    private OffSet moveOffset = new OffSet(0f, 0f);

// 双击手势监听
class PhotoGestureListener extends GestureDetector.SimpleOnGestureListener {
// 滑动时候触发 类似 ACTION_MOVE 事件
        @Override
        public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {

			// 如果是放大状态才能移动
            if (isDoubleClick) {
                moveOffset.setX(moveOffset.getX() - distanceX);
                moveOffset.setY(moveOffset.getY() - distanceY);
                // kotlin 写法:
                // moveOffset.x -= distanceX
                // moveOffset.y -= distanceY
                invalidate();
            }
            return super.onScroll(e1, e2, distanceX, distanceY);
        }
}

有同学可能要问了,这里为么是减等于(累减),首先要搞清楚这个distanceX 和 distanceY是什么

因为onScroll() 相当于是MOVE事件,所以这里只要是触摸就会输出

一张图搞懂,以x轴举例:

在这里插入图片描述

得出结论,以按压点为中心点

  • distanceX
    • 向左滑动 正数
    • 向右滑动 负数
  • distanceY
    • 向上滑动 正数
    • 向下滑动 负数

distanceX = 新的x坐标 - 旧的x坐标
distanceY = 新的y坐标 - 旧的y坐标

接下来看看移动画布的api:

#PhotoView2.java

@Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        /*
         * 作者:android 超级兵
         * 创建时间: 10/15/21 5:17 PM
         * TODO 平移画布
         * 	参数一:x轴平移距离
         * 	参数二:y轴平移距离
         */
        canvas.translate(-300, 0);
}

效果:


得出结论:
想要图片想左移动,设置 canvas.translate();x轴(参数一),为负数,反之向右移动设置为正数

知道了distanceX和distanceY,也知道了画布移动的api,那么问题就来了,移动时候为什么是减等于呢?

#PhotoGestureListener.java

// 如果是放大状态才能移动
if (isDoubleClick) {
	 // java写法
     moveOffset.setX(moveOffset.getX() - distanceX);
     moveOffset.setY(moveOffset.getY() - distanceY);
     // kotlin 写法:
     // moveOffset.x -= distanceX
     // moveOffset.y -= distanceY
     invalidate();
}

因为向左滑动的时候,图片应该是向右移动

又因为向左滑动时候,distanceX 为正数,并且是MOVE事件触发的,所以会触发多次

所以说这里是减等于,需要吧 distanceX的坐标都累加起来

最后,记得在onDraw中绘制偏移量哦

PohtoView2.java

  @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        // 移动画布
	 	canvas.translate(moveOffset.getX(), moveOffset.getY());
		.... 其余代码...
	
	}

当前的效果


图片放大状态操作

先来看看我说的这句《图片放大状态操作》是什么意思,直接看图
请添加图片描述
其实就是放大状态,禁止出现白边,使得用户体验更高!

在这里插入图片描述
来看看代码吧:

#PohtoView2.java

 public void fixOffset() {
        // 当前图片放大后的宽
        float currentWidth = mBitMap.getWidth() * bigScale;
        // 当前图片放大后的高
        float currentHeight = mBitMap.getHeight() * bigScale;

        // 右侧限制
        moveOffset.setX(Math.max(moveOffset.getX(), -(currentWidth - getWidth()) / 2));

        // 左侧限制 [左侧moveOffset.getX()为负数]
        moveOffset.setX(Math.min(moveOffset.getX(), (currentWidth - getWidth()) / 2));

        // 下侧限制
        moveOffset.setY(Math.max(moveOffset.getY(), -(currentHeight - getHeight()) / 2));

        // 上侧限制 [上侧moveOffset.getY()为负数]
        moveOffset.setY(Math.min(moveOffset.getY(), (currentHeight - getHeight()) / 2));
    }

在滑动过程中[onScroll()]限制一下即可!

#PhotoGestureListener.java

 		// 滑动时候触发 类似 ACTION_MOVE 事件
        @Override
        public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {

            if (isDoubleClick) {

                moveOffset.setX(moveOffset.getX() - distanceX);
                moveOffset.setY(moveOffset.getY() - distanceY);
				// moveOffset.x -= distanceX;
				// moveOffset.y -= distanceY;
				
                // 禁止图片滑动到屏幕外面
                fixOffset();

                invalidate();
            }

            return super.onScroll(e1, e2, distanceX, distanceY);
        }

这段代码需要《细细品味》一下!

当前的效果


双击放大到具体位置

名字很抽象,先来看看效果图:

辅助图:
在这里插入图片描述

实现思路:
当是小图片时候需要双击放大,只需要求出双击的位置到双击放大后对应的位置的距离,然后在平移过去即可

由上图可知:
缩小状态下双击的位置辅助线A 的距离 = e.getX() - getWidth() / 2

由于大图的宽 = getWidth() * bigScale

同理,所以大图对应小图点击的位置就是 (e.getX() - getWidth() / 2) * bigSale

那么缩小状态下双击的位置放大后对应的位置 = e.getX() - getWidth() / 2 - (e.getX() - getWidth() / 2) * bigSale

在双击放大时候进行移动即可:

#PhotoGestureListener.java

        @Override
        public boolean onDoubleTap(MotionEvent e) {
            isDoubleClick = !isDoubleClick;
            if (isDoubleClick) {
                float currentX = e.getX() - (float) getWidth() / 2f;
                float currentY = e.getY() - (float) getHeight() / 2f;
                
                moveOffset.setX(currentX - currentX * bigScale);
                moveOffset.setY(currentY - currentY * bigScale);

                // 重新计算,禁止放大后出现白边[具体实现在[图片放大状态操作]中娓娓道来过]
                fixOffset();

                scaleAnimation(currentScale, bigScale).start();
            } else {
                scaleAnimation(bigScale, smallScale).start();
            }
            return super.onDoubleTap(e);
        }

来看看效果:


shit,这…是什么…好像从某种意义上来说也是对的,最起码点击时候,平移是正确的,冷静分析,看看是什么问题…

经过长达30分钟的思考,终于知道是为什么了.

问题就在于: 直接双击的时候,直接就计算出了小图与大图之前的距离,然后底下还有一个缩放的动画,所以导致这种情况发生,只要让moveOffset也是跟随着缩放动画来改变即可!

目前知道的双击放大缩小条件有:

  • 双击放大是从currentScale -> bigScale 的改变
  • 双击缩小是从bigScale -> smallScale 的改变

这里引出了一个小算法:

 float a = (currentScale - smallScale) / (bigScale - smallScale);

假设当前是从小图缩放到大图 也就是从currentScale -> bigScale 的改变

当currentScale = bigScale 时候证明已经放大最大

所以 (currentScale - smallScale) / (bigScale - smallScale) = 1

否则的话

  • 双击放大:
    (currentScale - smallScale) / (bigScale - smallScale) 就是从0 - 1 的状态改变
  • 双击缩小:
    (currentScale - smallScale) / (bigScale - smallScale) 就是从1 - 0 的状态改变

来看看代码如何写:

#PhotoView2.java

 @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        /*
         * 作者:android 超级兵
         * 创建时间: 10/15/21 5:17 PM
         * TODO 平移画布
         *    	参数一:x 轴平移距离
         * 	    参数二:y 轴平移距离
         */
        float a = (currentScale - smallScale) / (bigScale - smallScale);
        canvas.translate(moveOffset.getX() * a, moveOffset.getY() * a);
        
        ....省略了好多代码....
      
}

这段代码需要《细细品》

当前的效果


图片放大状态加入Fling效果

先来看看要实现的效果:


既然说到fling,那就得借助android中fling的类 OverScroller

使用很简单,纯调api的代码

#PhotoView2.java

	// 惯性滑动
    private final OverScroller mOverScroller;
	@SuppressLint("CustomViewStyleable")
    public PhotoView2(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
		
		//  惯性滑动
        mOverScroller = new OverScroller(context);
	}

在onFling事件中调用:

#PhotoGestureListener.java

		 // 滑翔/飞翔 [惯性滑动]
        @Override
        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
            /*
             * int startX       滑动x
             * int startY,      滑动y
             * int velocityX,   每秒像素为单位[x轴]
             * int velocityY,   每秒像素为单位[y轴]
             * int minX,        宽最小值
             * int maxX,        宽最大值
             * int minY,        高最小值
             * int maxY,        高最大值
             * int overX,       溢出x的距离
             * int overY        溢出y的距离
             * 
             * 这里可以理解为吧滑动距离保存在了mOverScroller.fling()中
             */
            mOverScroller.fling(
                    (int) moveOffset.getX(),
                    (int) moveOffset.getY(),
                    (int) velocityX,
                    (int) velocityY,
                    (int) (-(mBitMap.getWidth() * bigScale - getWidth()) / 2),
                    (int) ((mBitMap.getHeight() * bigScale - getWidth()) / 2),
                    (int) (-(mBitMap.getHeight() * bigScale - getHeight()) / 2),
                    (int) ((mBitMap.getHeight() * bigScale - getHeight()) / 2),
                    300,
                    300
            );
            return super.onFling(e1, e2, velocityX, velocityY);
        }

来看看效果:

这…好像有点拉,并没有实现想要的效果…通过打印log知道,onFling()过程中他只会执行一次

所以需要吧保存在mOverScroller.fling()中的值取出来

#PhotoView2.java

	// 惯性滑动辅助
    class FlingRunner implements Runnable {

        @Override
        public void run() {
            // 判断当前是否是执行
            if (mOverScroller.computeScrollOffset()) {
                // 设置fling的值
                moveOffset.setX(mOverScroller.getCurrX());
                moveOffset.setY(mOverScroller.getCurrY());
                Log.i("szjFlingRunner", "X:" + mOverScroller.getCurrX() + "\tY:" + mOverScroller.getCurrY());

                // 继续执行FlingRunner.run
                postOnAnimation(this);
                // 刷新
                invalidate();
            }
        }
    }

还是在构造中初始化

#PhotoView2.java

	// 辅助惯性滑动类
    private final FlingRunner mFlingRunner;
@SuppressLint("CustomViewStyleable")
    public PhotoView2(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
		....省略了...
	  	// 惯性滑动辅助类
        mFlingRunner = new FlingRunner();

	}
#PhotoGestureListener.java

 		// 滑翔/飞翔 [惯性滑动]
        @Override
        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
            Log.i("szjPhotoGestureListener", "惯性滑动 onFling");

            Log.i("szjOnFling", "velocityX:" + velocityX + "\tvelocityY" + velocityY);

            /*
             * int startX       滑动x
             * int startY,      滑动y
             * int velocityX,   每秒像素为单位[x轴]
             * int velocityY,   每秒像素为单位[y轴]
             * int minX,        宽最小值
             * int maxX,        宽最大值
             * int minY,        高最小值
             * int maxY,        高最大值
             * int overX,       溢出x的距离
             * int overY        溢出y的距离
             */
            mOverScroller.fling(
            	....太长了,已被省略...
            	);
			
			// 设置fling效果
           mFlingRunner.run();

            return super.onFling(e1, e2, velocityX, velocityY);
        }

当前的效果


双指操作

双指操作还是继续使用android中自带的

class PhotoDoubleScaleGestureListener implements ScaleGestureDetector.OnScaleGestureListener {
        // 在双指操作开始时候获取当前缩放值
        private float scaleFactor = 0f;


        // 双指操作
        @Override
        public boolean onScale(ScaleGestureDetector detector) {
            // detector.getScaleFactor 缩放因子
            currentScale = scaleFactor * detector.getScaleFactor();
            
			// 刷新
            invalidate();
            return false;
        }

        // 双指操作开始
        @Override
        public boolean onScaleBegin(ScaleGestureDetector detector) {
            scaleFactor = currentScale;
            // 注意这里要为true 表示开始双指操作
            return true;
        }

        // 双指操作结束
        @Override
        public void onScaleEnd(ScaleGestureDetector detector) {

        }
    }

双指操作还是比较简单的,就是单纯的调调api即可

双指操作初始化

还是在构造中初始化

#PohtoView2.java

    // 双指操作
    private final ScaleGestureDetector scaleGestureDetector;
    
	@SuppressLint("CustomViewStyleable")
    public PhotoView2(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

		 // 双指头操作
        scaleGestureDetector = new ScaleGestureDetector(context, new PhotoDoubleScaleGestureListener());
}

双指操作也需要在onTouchEvent()中初始化

因为 双指操作和双击操作都是一样的,都是一个事件

 	@Override
    public boolean onTouchEvent(MotionEvent event) {

        // 双指操作
        boolean scaleTouchEvent = scaleGestureDetector.onTouchEvent(event);

        // 是否是双指操作
        if (scaleGestureDetector.isInProgress()) {

            return scaleTouchEvent;
        }

        // 双击操作
        return mPhotoGestureListener.onTouchEvent(event);
    }

现在默认的事件的是双指操作事件优先,其次是双击操作

当前的效果


最终优化双击双指操作

可以看到,基本的已经实现了,现在只需要最终限制一下就可以了!

这段代码没有什么含金量,直接来看代码:

#PhotoDoubleScaleGestureListener.java

 		// 双指操作结束
        @Override
        public void onScaleEnd(ScaleGestureDetector detector) {
            // 当前图片宽
            float currentWidth = mBitMap.getWidth() * currentScale;
            // 缩放前的图片宽
            float smallWidth = mBitMap.getWidth() * smallScale;
            // 缩放后的图片宽
            float bigWidth = mBitMap.getWidth() * bigScale;

            // 如果当前图片 < 缩放前的图片
            if (currentWidth < smallWidth) {
                // 图片缩小
                isDoubleClick = false;

                scaleAnimation(currentScale, smallScale).start();
            } else if (currentWidth > smallWidth) {
                // 图片缩小
                isDoubleClick = false;
            }

            // 如果当前状态 > 缩放后的图片 那么就让他改变为最大的状态
            if (currentWidth > bigWidth) {

                //  双击时 图片缩小
                scaleAnimation(currentScale, bigScale).start();
                // 双击时候 图片放大
                isDoubleClick = true;
            }
        }

最终效果



完整代码

原创不易,您的点赞就是对我最大的支持!

PhotoView PhotoView aims to help produce an easily usable implementation of a zooming Android ImageView. Branch Develop: Build Status Branch Master: Build Status PhotoView Features Out of the box zooming, using multi-touch and double-tap. Scrolling, with smooth scrolling fling. Works perfectly when used in a scrolling parent (such as ViewPager). Allows the application to be notified when the displayed Matrix has changed. Useful for when you need to update your UI based on the current zoom/scroll position. Allows the application to be notified when the user taps on the Photo. Sample Application The sample application (the source is in the repository) has been published onto Google Play for easy access: Get it on Google Play Gradle Dependency Add this in your root build.gradle file (not your module build.gradle file): allprojects { repositories { ... maven { url "https://jitpack.io" } } } Then, add the library to your project build.gradle dependencies { compile 'com.github.chrisbanes:PhotoView:1.2.6' } Sample Usage There is a sample provided which shows how to use the library in a more advanced way, but for completeness here is all that is required to get PhotoView working: ImageView mImageView; PhotoViewAttacher mAttacher; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // Any implementation of ImageView can be used! mImageView = (ImageView) findViewById(R.id.iv_photo); // Set the Drawable displayed Drawable bitmap = getResources().getDrawable(R.drawable.wallpaper); mImageView.setImageDrawable(bitmap); // Attach a PhotoViewAttacher, which takes care of all of the zooming functionality. // (not needed unless you are going to change the drawable later) mAttacher = new PhotoViewAttacher(mImageView); } // If you later call mImageView.setImageDrawable/setImageBitmap/setImageResource/etc then you just need to call mAttacher.update(); Issues With ViewGroups There are some ViewGroups (ones that utilize onInterceptTouchEvent) that throw exceptions when a PhotoView is placed within them, most notably ViewPager and DrawerLayout. This is a framework issue that has not been resolved. In order to prevent this exception (which typically occurs when you zoom out), take a look at HackyDrawerLayout and you can see the solution is to simply catch the exception. Any ViewGroup which uses onInterceptTouchEvent will also need to be extended and exceptions caught. Use the HackyDrawerLayout as a template of how to do so. The basic implementation is: public class HackyProblematicViewGroup extends ProblematicViewGroup { public HackyProblematicViewGroup(Context context) { super(context); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { try { return super.onInterceptTouchEvent(ev); } catch (IllegalArgumentException e) { //uncomment if you really want to see these errors //e.printStackTrace(); return false; } } } Usage with Fresco Due to the complex nature of Fresco, this library does not currently support Fresco. See this project as an alternative solution. Subsampling Support This library aims to keep the zooming implementation simple. If you are looking for an implementation that supports subsampling, check out this project Pull Requests / Contribution Development happens in develop branch of this repository, and Pull Requests should be filled against that branch. Any Pull Request against master will be rejected License Copyright 2011, 2012 Chris Banes Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

s10g

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值