我們就把這個問題叫做圖片查看器吧,它的主要功能有: 1、雙擊縮放圖片。 2、 雙指縮放圖片。 3、單指拖拽圖片。 為此這個圖片查看器需要考慮以下的技術點: 一、雙擊縮放圖片: 1、如果圖片高度比屏幕的高度小得多,那麼就將圖片放大到高度與屏幕高度相等,否則就放大一個特定的倍數。 2、如何判斷是否到達這 ...
我們就把這個問題叫做圖片查看器吧,它的主要功能有:
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);