Android 在viewPager中双指缩放图片双击缩放图片单指拖拽图片的实现思路

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

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也一样继续拦截了事件。目前没解决。

以上所述是小编给大家介绍的在viewPager中双指缩放图片双击缩放图片单指拖拽图片的实现思路,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对脚本之家网站的支持!

时间: 2024-09-07 21:03:32

Android 在viewPager中双指缩放图片双击缩放图片单指拖拽图片的实现思路的相关文章

如何在OS X El Capitan中开启三指拖拽手势?

  一直以来,苹果Macbook的控制板手势和OS X操作系统的结合为用户带来更加完善的体验,OS X的按方式和多指手势也因此而广受用户欢迎.不过最近有不少用户发现,在苹果最新的OS X El Capitan测试版中,三指拖拽手势不见了! Mac的触控板手势可以让OS X的体验更加完善,其点按方式和多指手势更是受到了广泛的欢迎,三指拖拽手势更是其中的佼佼者. 不过在OS X El Capitan中,不少用户都表示苹果已经移除了三指拖拽的手势,因为在原来的位置"系统偏好设置"-"

基于touch.js手势库+zepto.js插件开发图片查看器(滑动、缩放、双击缩放)_javascript技巧

最近由于公司项目需要图片查看器,网上搜了一圈,感觉资料很少,所以决定基于百度的touch.js手势库+zepto.js自己写了一个小插件,实现了左右滑动,双指缩放,双击缩放功能,基本使用还行,但是有时候还是不太顺畅,后续会慢慢完善:写的不好的地方望各位能够给出好的建议,谢谢! 源码地址:https://github.com/GLwen/molong_photoSwipe.git 演示:http://runjs.cn/detail/iceaaogh molong.css *{padding:0;m

ist iew-跪求:Android在listview中item没有type,如何控制展示不同类型的图片

问题描述 跪求:Android在listview中item没有type,如何控制展示不同类型的图片 在一个界面中,一个接口有很多的图片,一张大图,下面4张小图,在接着两个大图,后面两张小图,就这个模式展示,想不到用什么方法. 解决方案 没理解你这是什么样的布局,想要展示成什么样. 解决方案二: 这个不是在布局文件里布局好的,然后在代码里设置好,最后填充进listview的吗? 如果回答对您有帮助,请采纳 解决方案三: 确实没有理解到你想要做成什么效果 解决方案四: 如果没理解错的话你可以用瀑布流

Java小例子:创建一个可拖拽图片的面板

今天在论坛上看到帖子希望能在 Swing 中实现像拖地图一样拖拽图片.这里是一个最 简单的实现,提供了一个基本思路. import javax.swing.*; import javax.swing.filechooser.FileNameExtensionFilter; import java.awt.*; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.event.M

JS实现放大、缩小及拖拽图片的方法【可兼容IE、火狐】_javascript技巧

本文实例讲述了JS实现放大.缩小及拖拽图片的方法.分享给大家供大家参考,具体如下: var divId; var v_left; var v_top; window.onload=function(){ divId = document.getElementById("block1"); var height1 = images1.height;//图片的高度 var width1 = images1.width;//图片的宽度 v_left=(document.body.client

原生JS实现拖拽图片效果_javascript技巧

本文实例为大家讲解了JS实现拖拽图片效果的详细代码,分享给大家供大家参考,具体内容如下 javascript event 对象的clientX,offsetX,screenX,pageX的区别: 用html5的drag来实现拖拽有兼容性问题,使用拖拽插件代码又很多,而这个拖拽demo代码少,并且兼容所有浏览器,很值得在项目中使用, css样式如下: #div1{ width: 100px; height: 100px; background-color: #4D4D4D; position: a

右击拖拽不弹出对话框-鼠标右键拖拽图片超链接,拖拽完成后如何不弹出对话框

问题描述 鼠标右键拖拽图片超链接,拖拽完成后如何不弹出对话框 鼠标右键拖拽图片超链接,拖拽完成后如何不弹出对话框,我已经可以用右键拖拽了,但是完成后他会弹出右键的对话框.该如何编写代码 解决方案 mousedown移除href属性,mouseup的时候判断是否移动过,如果移动过就设置href触发跳转,要不延时设置href即可. <style>a{position:absolute;}</style> <script type="text/javascript&quo

详解Android 在 ViewPager 中使用 Fragment 的懒加载

我们先看一下效果: 首先,我们要知道什么是懒加载: 懒加载,就是先初始化控件,在用户可见的时候再加载数据. 为什么要懒加载? 懒加载多被使用在新闻资讯类客户端中,试想那么多的分类如果一下子都加载出来,真的是极大地消耗了系统资源.可能有人会说 ViewPager 有 viewPager.setOffscreenPageLimit() 的方法,我们传个 0 进去不就好了吗?看过源码的应该知道,即便你传了 0 进去,系统也会默认为 1 的,也就是 ViewPager 依然会加载当前页面的前后各一个 F

javascript 网页编辑框及拖拽图片的问题_网页编辑器

复制代码 代码如下: function keyPress(ev){ if(ev.keyCode==13){ //在光标所在处创建一个区域. var range=document.selection.createRange(); //将区域的内容设置成为换行符. range.text="\n"; //将区域的起始点向右移动一个字符的长度. range.moveStart("character", 1); //将光标移动到区域的结尾.(若为false则是移动到起始位置)