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;
}
}
最终效果

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