Android 在viewPager中雙指縮放圖片雙擊縮放圖片單指拖拽圖片的實現思路

NO IMAGE
1 Star2 Stars3 Stars4 Stars5 Stars 給文章打分!
Loading...

我們就把這個問題叫做圖片檢視器吧,它的主要功能有:

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中雙指縮放圖片雙擊縮放圖片單指拖拽圖片的實現思路,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回覆大家的。在此也非常感謝大家對指令碼之家網站的支援!

您可能感興趣的文章:

Android中RecyclerView實現多級摺疊列表效果(二)Android 通過Base64上傳圖片到伺服器實現例項Android 開啟相簿選擇單張圖片實現程式碼Android 實現ViewPager邊界回彈效果例項程式碼Android 實現自動打電話與發簡訊的例項Android 中 android.view.WindowLeaked的解決辦法

相關文章

Android 開發 最新文章