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

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

PhotoView, android 图解 PhotoView 从0到1,从 👎 到👍

⚠️:考虑到部分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;
            }
        }
复制代码

最终效果



完整代码

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

文章分类
Android
文章标签