Android GestureDetector ScaleGestureDetector
Android程式碼中給我們提供大量的幫助類來方便我們的使用。今天咱們就來看下手勢幫助類GestureDetector、ScaleGestureDetector。
一、GestureDetector
Android手機螢幕上,當咱們觸控式螢幕幕的時候,會產生許多手勢事件,如down,up,scroll,filing等等。咱們可以在onTouchEvent()方法裡面完成各種手勢識別。但是,咱們自己去識別各種手勢就比較麻煩了,而且有些情況可能考慮的不是那麼的全面。所以,為了方便咱們的時候Android就給提供了GestureDetector幫助類來方便大家的使用。
GestureDetector類給我們提供了三個介面,一個外部類。
- OnGestureListener:介面,用來監聽手勢事件(6種)。
- OnDoubleTapListener:介面,用來監聽雙擊事件。
- OnContextClickListener:介面,外接裝置,比如外接滑鼠產生的事件(本文中我們不考慮)。
- SimpleOnGestureListener:外部類,SimpleOnGestureListener其實上面三個介面中所有函式的整合,它包含了這三個接口裡所有必須要實現的函式而且都已經重寫,但所有方法體都是空的。需要自己根據情況去重寫。
OnGestureListener介面方法解釋:
public interface OnGestureListener {
/**
* 按下。返回值表示事件是否處理
*/
boolean onDown(MotionEvent e);
/**
* 短按(手指尚未鬆開也沒有達到scroll條件)
*/
void onShowPress(MotionEvent e);
/**
* 輕觸(手指鬆開)
*/
boolean onSingleTapUp(MotionEvent e);
/**
* 滑動(一次完整的事件可能會多次觸發該函式)。返回值表示事件是否處理
*/
boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY);
/**
* 長按(手指尚未鬆開也沒有達到scroll條件)
*/
void onLongPress(MotionEvent e);
/**
* 滑屏(使用者按下觸控式螢幕、快速滑動後鬆開,返回值表示事件是否處理)
*/
boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY);
}
OnDoubleTapListener介面方法解釋:
public interface OnDoubleTapListener {
/**
* 單擊事件(onSingleTapConfirmed,onDoubleTap是兩個互斥的函式)
*/
boolean onSingleTapConfirmed(MotionEvent e);
/**
* 雙擊事件
*/
boolean onDoubleTap(MotionEvent e);
/**
* 雙擊事件產生之後手指還沒有擡起的時候的後續事件
*/
boolean onDoubleTapEvent(MotionEvent e);
}
SimpleOnGestureListener實現了OnGestureListener、OnDoubleTapListener、OnContextClickListener。SimpleOnGestureListener裡面的方法是是三個介面的集合。
1.1、GestureDetector使用
GestureDetector的使用非常的簡單,分為三個步驟:
- 定義GestureDetector類,
- 將touch事件交給GestureDetector(onTouchEvent函式裡面呼叫GestureDetector的onTouchEvent函式)。
- 處理SimpleOnGestureListener或者OnGestureListener、OnDoubleTapListener、OnContextClickListener三者之一的回撥。
我們用一個簡單的例項來說明GestureDetector的使用。我就簡單的寫一個View,然後看看各個事件的觸發情況。
public class GestureView extends View {
//定義GestureDetector類
private GestureDetector mGestureDetector;
public GestureView(Context context) {
this(context, null);
}
public GestureView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public GestureView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mGestureDetector = new GestureDetector(context, mOnGestureListener);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
return mGestureDetector.onTouchEvent(event);
}
private final GestureDetector.OnGestureListener mOnGestureListener = new GestureDetector.SimpleOnGestureListener() {
@Override
public boolean onSingleTapUp(MotionEvent e) {
Log.d("tuacy", "onSingleTapUp");
return true;
}
@Override
public void onLongPress(MotionEvent e) {
Log.d("tuacy", "onLongPress");
super.onLongPress(e);
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
Log.d("tuacy", "onScroll");
return true;
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
Log.d("tuacy", "onFling");
return true;
}
@Override
public void onShowPress(MotionEvent e) {
Log.d("tuacy", "onShowPress");
super.onShowPress(e);
}
@Override
public boolean onDown(MotionEvent e) {
Log.d("tuacy", "onDown");
return true;
}
@Override
public boolean onDoubleTap(MotionEvent e) {
Log.d("tuacy", "onDoubleTap");
return true;
}
@Override
public boolean onDoubleTapEvent(MotionEvent e) {
Log.d("tuacy", "onDoubleTapEvent");
return true;
}
@Override
public boolean onSingleTapConfirmed(MotionEvent e) {
Log.d("tuacy", "onSingleTapConfirmed");
return true;
}
};
}
我們總結下各個動作對應的回撥
- 快速點選下View:onDow() -> onSingleTapUp() -> onSingleTapConfirmed()。
- 短按View不滑動:onDown() -> onShowPress() -> onSingleTapUp() -> onSingleTapConfirmed()。
- 長按View不滑動:onDown() -> onShowPress() -> onLongPress()。
- 滑動:onDown() -> onScroll() -> onScroll()….。
- 快速滑動:onDown() -> onScroll() -> onScroll()…. -> onFling()。
- 快速點選兩下:onDown() -> onSingleTapUp() -> onDoubleTap() -> onDoubleTapEvent() -> onDoubleTapEvent()…。
GestureDetector的使用給一個建議,GestureDetector的所有回撥函式,有返回值的。如果你用到了就返回true。因為有些函式你不返回true的話可能後續的事件傳遞不進來。這裡我們可以給大家留一個問題,大家可以自己分下下返回false的情況對應的回撥順序。比如onDown()函式我們返回false,快速點選的時候回撥呼叫的情況。
1.2、GestureDetector原始碼解析
GestureDetector原始碼也不是很複雜,我們做一個非常簡單的分析。我們從建構函式開始。
public GestureDetector(Context context, OnGestureListener listener) {
this(context, listener, null);
}
public GestureDetector(Context context, OnGestureListener listener, Handler handler) {
if (handler != null) {
mHandler = new GestureHandler(handler);
} else {
mHandler = new GestureHandler();
}
mListener = listener;
if (listener instanceof OnDoubleTapListener) {
setOnDoubleTapListener((OnDoubleTapListener) listener);
}
if (listener instanceof OnContextClickListener) {
setContextClickListener((OnContextClickListener) listener);
}
init(context);
}
private void init(Context context) {
if (mListener == null) {
throw new NullPointerException("OnGestureListener must not be null");
}
mIsLongpressEnabled = true;
// Fallback to support pre-donuts releases
int touchSlop, doubleTapSlop, doubleTapTouchSlop;
if (context == null) {
//noinspection deprecation
touchSlop = ViewConfiguration.getTouchSlop();
doubleTapTouchSlop = touchSlop; // Hack rather than adding a hiden method for this
doubleTapSlop = ViewConfiguration.getDoubleTapSlop();
//noinspection deprecation
mMinimumFlingVelocity = ViewConfiguration.getMinimumFlingVelocity();
mMaximumFlingVelocity = ViewConfiguration.getMaximumFlingVelocity();
} else {
final ViewConfiguration configuration = ViewConfiguration.get(context);
touchSlop = configuration.getScaledTouchSlop();
doubleTapTouchSlop = configuration.getScaledDoubleTapTouchSlop();
doubleTapSlop = configuration.getScaledDoubleTapSlop();
mMinimumFlingVelocity = configuration.getScaledMinimumFlingVelocity();
mMaximumFlingVelocity = configuration.getScaledMaximumFlingVelocity();
}
mTouchSlopSquare = touchSlop * touchSlop;
mDoubleTapTouchSlopSquare = doubleTapTouchSlop * doubleTapTouchSlop;
mDoubleTapSlopSquare = doubleTapSlop * doubleTapSlop;
}
GestureDetector建構函式裡面的程式碼也不復雜,都是在設定一些變數。其中mHandler:用於sendMessage,mListener、mDoubleTapListener、mContextClickListener:三個介面的變數,mIsLongpressEnabled:是否支援長按操作,mMinimumFlingVelocity:fling的最小速度,mMaximumFlingVelocity:fling的最大速度,mTouchSlopSquare:用來判斷是否開始scroll,mDoubleTapTouchSlopSquare:判斷雙擊的時候用到,第一個單擊的時候產生了MotionEvent.ACTION_MOVE,並且move的距離超過了這個值 就不認為是雙擊事件,mDoubleTapSlopSquare:判斷雙擊的時候用到,兩次單擊範圍要在這個值之內。否則不算是雙擊事件。
分析完GestureDetector的建構函式,接下來我們直接看GestureDetector的onTouchEvent()函式,這個函式我們主要分析:MotionEvent.ACTION_DOWN、MotionEvent.ACTION_MOVE、MotionEvent.ACTION_UP三個事件的處理過程。
public boolean onTouchEvent(MotionEvent ev) {
// 這一部分是用於測試的,我們不用管
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(ev, 0);
}
final int action = ev.getAction();
// 用來記錄滑動速度
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
}
mVelocityTracker.addMovement(ev);
...
boolean handled = false;
switch (action & MotionEvent.ACTION_MASK) {
...
case MotionEvent.ACTION_DOWN:
if (mDoubleTapListener != null) {
boolean hadTapMessage = mHandler.hasMessages(TAP);
if (hadTapMessage) mHandler.removeMessages(TAP);
if ((mCurrentDownEvent != null) && (mPreviousUpEvent != null) && hadTapMessage &&
isConsideredDoubleTap(mCurrentDownEvent, mPreviousUpEvent, ev)) {
// This is a second tap
mIsDoubleTapping = true;
// Give a callback with the first tap of the double-tap
handled |= mDoubleTapListener.onDoubleTap(mCurrentDownEvent);
// Give a callback with down event of the double-tap
handled |= mDoubleTapListener.onDoubleTapEvent(ev);
} else {
// This is a first tap
mHandler.sendEmptyMessageDelayed(TAP, DOUBLE_TAP_TIMEOUT);
}
}
mDownFocusX = mLastFocusX = focusX;
mDownFocusY = mLastFocusY = focusY;
if (mCurrentDownEvent != null) {
mCurrentDownEvent.recycle();
}
mCurrentDownEvent = MotionEvent.obtain(ev);
mAlwaysInTapRegion = true;
mAlwaysInBiggerTapRegion = true;
mStillDown = true;
mInLongPress = false;
mDeferConfirmSingleTap = false;
// 用於處理長按事件處理 對應onLongPress()函式
if (mIsLongpressEnabled) {
mHandler.removeMessages(LONG_PRESS);
mHandler.sendEmptyMessageAtTime(LONG_PRESS,
mCurrentDownEvent.getDownTime() + LONGPRESS_TIMEOUT);
}
// 用於輕觸事件處理,對應onShowPress()函式
mHandler.sendEmptyMessageAtTime(SHOW_PRESS,
mCurrentDownEvent.getDownTime() + TAP_TIMEOUT);
handled |= mListener.onDown(ev);
break;
case MotionEvent.ACTION_MOVE:
if (mInLongPress || mInContextClick) {
break;
}
final float scrollX = mLastFocusX - focusX;
final float scrollY = mLastFocusY - focusY;
if (mIsDoubleTapping) {
// Give the move events of the double-tap
handled |= mDoubleTapListener.onDoubleTapEvent(ev);
} else if (mAlwaysInTapRegion) {
final int deltaX = (int) (focusX - mDownFocusX);
final int deltaY = (int) (focusY - mDownFocusY);
int distance = (deltaX * deltaX) + (deltaY * deltaY);
int slopSquare = isGeneratedGesture ? 0 : mTouchSlopSquare;
if (distance > slopSquare) {
handled = mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY);
mLastFocusX = focusX;
mLastFocusY = focusY;
mAlwaysInTapRegion = false;
mHandler.removeMessages(TAP);
mHandler.removeMessages(SHOW_PRESS);
mHandler.removeMessages(LONG_PRESS);
}
int doubleTapSlopSquare = isGeneratedGesture ? 0 : mDoubleTapTouchSlopSquare;
if (distance > doubleTapSlopSquare) {
mAlwaysInBiggerTapRegion = false;
}
} else if ((Math.abs(scrollX) >= 1) || (Math.abs(scrollY) >= 1)) {
handled = mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY);
mLastFocusX = focusX;
mLastFocusY = focusY;
}
break;
case MotionEvent.ACTION_UP:
mStillDown = false;
MotionEvent currentUpEvent = MotionEvent.obtain(ev);
if (mIsDoubleTapping) {
// Finally, give the up event of the double-tap
handled |= mDoubleTapListener.onDoubleTapEvent(ev);
} else if (mInLongPress) {
mHandler.removeMessages(TAP);
mInLongPress = false;
} else if (mAlwaysInTapRegion && !mIgnoreNextUpEvent) {
handled = mListener.onSingleTapUp(ev);
if (mDeferConfirmSingleTap && mDoubleTapListener != null) {
mDoubleTapListener.onSingleTapConfirmed(ev);
}
} else if (!mIgnoreNextUpEvent) {
// A fling must travel the minimum tap distance
final VelocityTracker velocityTracker = mVelocityTracker;
final int pointerId = ev.getPointerId(0);
velocityTracker.computeCurrentVelocity(1000, mMaximumFlingVelocity);
final float velocityY = velocityTracker.getYVelocity(pointerId);
final float velocityX = velocityTracker.getXVelocity(pointerId);
if ((Math.abs(velocityY) > mMinimumFlingVelocity)
|| (Math.abs(velocityX) > mMinimumFlingVelocity)){
handled = mListener.onFling(mCurrentDownEvent, ev, velocityX, velocityY);
}
}
if (mPreviousUpEvent != null) {
mPreviousUpEvent.recycle();
}
// Hold the event we obtained above - listeners may have changed the original.
mPreviousUpEvent = currentUpEvent;
if (mVelocityTracker != null) {
// This may have been cleared when we called out to the
// application above.
mVelocityTracker.recycle();
mVelocityTracker = null;
}
mIsDoubleTapping = false;
mDeferConfirmSingleTap = false;
mIgnoreNextUpEvent = false;
mHandler.removeMessages(SHOW_PRESS);
mHandler.removeMessages(LONG_PRESS);
break;
case MotionEvent.ACTION_CANCEL:
cancel();
break;
}
if (!handled && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(ev, 0);
}
return handled;
}
MotionEvent.ACTION_DOWN處理部分我們分四個部分來看:
- 對DoubleTapListener做處理:DoubleTapListener是用於處理雙擊事件,所以肯定是要有前後兩個事件的,我們可以看下大概的邏輯mCurrentDownEvent是前一次事件按下時候的MotionEvent,mPreviousUpEvent是前一次事件擡起是的的MotionEvent。從這段程式碼我們也能發現onSingleTapConfirmed()函式和onDoubleTap()兩個函式是互斥的。其中isConsideredDoubleTap()函式是用於判斷是否達到了雙擊事件的條件。mIsDoubleTapping表示產生了雙擊事件。
- 長按事件的處理,mHandler.sendEmptyMessageAtTime(LONG_PRESS,
mCurrentDownEvent.getDownTime() + LONGPRESS_TIMEOUT); 傳送了一個LONG_PRESS型別的延時message。至於長按事件會不會觸發,就要看LONG_PRESS對應的message在LONGPRESS_TIMEOUT時間內會不會被remove掉。 - 輕觸事件的處理,mHandler.sendEmptyMessageAtTime(SHOW_PRESS,
mCurrentDownEvent.getDownTime() + TAP_TIMEOUT);傳送了一個SHOW_PRESS型別的延時message。同樣輕觸事件會不會觸發也的看後續SHOW_PRESS對應的message會不會被remove掉。 - 呼叫onDown()函式,handled |= mListener.onDown(ev);
MotionEvent.ACTION_DOWN的時候我們還得關注下返回值,只有返回true才能保證後續事件在進入到onTouchEvent()函式裡面來。
MotionEvent.ACTION_MOVE處理部分
MotionEvent.ACTION_MOVE部分邏輯處理。一開始判斷是否產生了長按事件,產生了長按事件直接break掉。接下來關鍵點在裡面的if else。mIsDoubleTapping:表示產生了雙擊事件,mAlwaysInTapRegion:表示是否進入了滑動狀態。從邏輯處理過程可以看到產生了滑動事件就會把TAP,SHOW_PRESS,LONG_PRESS對應的訊息都移除掉。
MotionEvent.ACTION_UP處理部分
MotionEvent.ACTION_UP的邏輯也不難,如果產生了雙擊事件就回調onDoubleTapEvent()函式,如果還沒有進入滑動的狀態就回調onSingleTapUp(),然後再看要不要回調onSingleTapConfirmed()函式,這裡咱們也能發現產生了雙擊事件就不會回撥onSingleTapConfirmed()函式。最後就是onFling()函式的回撥。
二、ScaleGestureDetector
ScaleGestureDetector是用於處理縮放的工具類,用法與GestureDetector類似,都是通過onTouchEvent()關聯相應的MotionEvent事件。
ScaleGestureDetector類給提供了OnScaleGestureListener介面,來告訴我們縮放的過程中的一些回撥。
OnScaleGestureListener回撥函式介紹
public interface OnScaleGestureListener {
/**
* 縮放進行中,返回值表示是否下次縮放需要重置,如果返回ture,那麼detector就會重置縮放事件,如果返回false,detector會在之前的縮放上繼續進行計算
*/
public boolean onScale(ScaleGestureDetector detector);
/**
* 縮放開始,返回值表示是否受理後續的縮放事件
*/
public boolean onScaleBegin(ScaleGestureDetector detector);
/**
* 縮放結束
*/
public void onScaleEnd(ScaleGestureDetector detector);
}
ScaleGestureDetector類常用函式介紹,因為在縮放的過程中,要通過ScaleGestureDetector來獲取一些縮放資訊。
/**
* 縮放是否正處在進行中
*/
public boolean isInProgress();
/**
* 返回組成縮放手勢(兩個手指)中點x的位置
*/
public float getFocusX();
/**
* 返回組成縮放手勢(兩個手指)中點y的位置
*/
public float getFocusY();
/**
* 組成縮放手勢的兩個觸點的跨度(兩個觸點間的距離)
*/
public float getCurrentSpan();
/**
* 同上,x的距離
*/
public float getCurrentSpanX();
/**
* 同上,y的距離
*/
public float getCurrentSpanY();
/**
* 組成縮放手勢的兩個觸點的前一次縮放的跨度(兩個觸點間的距離)
*/
public float getPreviousSpan();
/**
* 同上,x的距離
*/
public float getPreviousSpanX();
/**
* 同上,y的距離
*/
public float getPreviousSpanY();
/**
* 獲取本次縮放事件的縮放因子,縮放事件以onScale()返回值為基準,一旦該方法返回true,代表本次事件結束,重新開啟下次縮放事件。
*/
public float getScaleFactor();
/**
* 返回上次縮放事件結束時到當前的時間間隔
*/
public long getTimeDelta();
/**
* 獲取當前motion事件的時間
*/
public long getEventTime();
2.1、ScaleGestureDetector使用
ScaleGestureDetector的使用也是簡單的分為三步。
- 定義ScaleGestureDetector類,
- 將touch事件交給ScaleGestureDetector(onTouchEvent函式裡面呼叫ScaleGestureDetector的onTouchEvent函式)。
- 處理OnScaleGestureListener各個回撥。
接下來我們通過重寫ImageView,使用ScaleGestureDetector來實現圖片的縮放功能。
程式碼是網上找的
public class ScaleImageView extends AppCompatImageView
implements ScaleGestureDetector.OnScaleGestureListener, ViewTreeObserver.OnGlobalLayoutListener {
private static final int MAX_SCALE_TIME = 4;
private ScaleGestureDetector mScaleGestureDetector;
// 縮放工具類
private Matrix mMatrix;
private boolean mFirstLayout;
private float mBaseScale;
public ScaleImageView(Context context) {
this(context, null);
}
public ScaleImageView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public ScaleImageView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mScaleGestureDetector = new ScaleGestureDetector(context, this);
mMatrix = new Matrix();
setScaleType(ScaleType.MATRIX);
mFirstLayout = true;
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
getViewTreeObserver().addOnGlobalLayoutListener(this);
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
//移除OnGlobalLayoutListener
getViewTreeObserver().removeGlobalOnLayoutListener(this);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
return mScaleGestureDetector.onTouchEvent(event);
}
/**
* 縮放進行中
*/
@Override
public boolean onScale(ScaleGestureDetector detector) {
if (null == getDrawable() || mMatrix == null) {
return true;
}
// 獲取縮放因子
float scaleFactor = detector.getScaleFactor();
float scale = getScale();
// 控制元件圖片的縮放範圍
if ((scale < mBaseScale * MAX_SCALE_TIME && scaleFactor > 1.0f) || (scale > mBaseScale && scaleFactor < 1.0f)) {
if (scale * scaleFactor < mBaseScale) {
scaleFactor = mBaseScale / scale;
}
if (scale * scaleFactor > mBaseScale * MAX_SCALE_TIME) {
scaleFactor = mBaseScale * MAX_SCALE_TIME / scale;
}
// 以螢幕中央位置進行縮放
mMatrix.postScale(scaleFactor, scaleFactor, detector.getFocusX(), detector.getFocusY());
borderAndCenterCheck();
setImageMatrix(mMatrix);
}
return false;
}
/**
* 縮放開始
*/
@Override
public boolean onScaleBegin(ScaleGestureDetector detector) {
return true;
}
/**
* 縮放結束
*/
@Override
public void onScaleEnd(ScaleGestureDetector detector) {
}
@Override
public void onGlobalLayout() {
if (mFirstLayout) {
mFirstLayout = false;
// 獲取控制元件的寬度和高度
int viewWidth = getWidth();
int viewHeight = getHeight();
// 獲取到ImageView對應圖片的寬度和高度
Drawable drawable = getDrawable();
if (null == drawable) {
return;
}
// 圖片固有寬度
int drawableWidth = drawable.getIntrinsicWidth();
// 圖片固有高度
int drawableHeight = drawable.getIntrinsicHeight();
// 接下來對圖片做初始的縮放處理,保證圖片能看全
if (drawableWidth >= viewWidth && drawableHeight >= viewHeight) {
// 圖片寬度和高度都大於控制元件(縮小)
mBaseScale = Math.min(viewWidth * 1.0f / drawableWidth, viewHeight * 1.0f / drawableHeight);
} else if (drawableWidth > viewWidth && drawableHeight < viewHeight) {
// 圖片寬度大於控制元件,高度小於控制元件(縮小)
mBaseScale = viewWidth * 1.0f / drawableWidth;
} else if (drawableWidth < viewWidth && drawableHeight > viewHeight) {
// 圖片寬度小於控制元件,高度大於控制元件(縮小)
mBaseScale = viewHeight * 1.0f / drawableHeight;
} else {
// 圖片寬度小於控制元件,高度小於控制元件(放大)
mBaseScale = Math.min(viewWidth * 1.0f / drawableWidth, viewHeight * 1.0f / drawableHeight);
}
// 將圖片移動到手機螢幕的中間位置
float distanceX = viewWidth / 2 - drawableWidth / 2;
float distanceY = viewHeight / 2 - drawableHeight / 2;
mMatrix.postTranslate(distanceX, distanceY);
mMatrix.postScale(mBaseScale, mBaseScale, viewWidth / 2, viewHeight / 2);
setImageMatrix(mMatrix);
}
}
private float getScale() {
float[] values = new float[9];
mMatrix.getValues(values);
return values[Matrix.MSCALE_X];
}
/**
* 獲得圖片放大縮小以後的寬和高
*/
private RectF getMatrixRectF() {
RectF rectF = new RectF();
Drawable drawable = getDrawable();
if (drawable != null) {
rectF.set(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
mMatrix.mapRect(rectF);
}
return rectF;
}
/**
* 圖片在縮放時進行邊界控制
*/
private void borderAndCenterCheck() {
RectF rect = getMatrixRectF();
float deltaX = 0;
float deltaY = 0;
int viewWidth = getWidth();
int viewHeight = getHeight();
// 縮放時進行邊界檢測,防止出現白邊
if (rect.width() >= viewWidth) {
if (rect.left > 0) {
deltaX = -rect.left;
}
if (rect.right < viewWidth) {
deltaX = viewWidth - rect.right;
}
}
if (rect.height() >= viewHeight) {
if (rect.top > 0) {
deltaY = -rect.top;
}
if (rect.bottom < viewHeight) {
deltaY = viewHeight - rect.bottom;
}
}
// 如果寬度或者高度小於控制元件的寬或者高;則讓其居中
if (rect.width() < viewWidth) {
deltaX = viewWidth / 2f - rect.right + rect.width() / 2f;
}
if (rect.height() < viewHeight) {
deltaY = viewHeight / 2f - rect.bottom + rect.height() / 2f;
}
mMatrix.postTranslate(deltaX, deltaY);
}
}
2.2、ScaleGestureDetector原始碼分析
ScaleGestureDetector的原始碼比GestureDetector的原始碼就要稍微複雜點了,因為ScaleGestureDetector的事件涉及到多個手指。
想要縮放的值,所有的MotionEvent事件都要交給ScaleGestureDetector的onTouchEvent()函式,所以我們就先直接來看下onTouchEvent()函式大概的邏輯。
public boolean onTouchEvent(MotionEvent event) {
...
// 縮放的手勢是需要多個手指來完成的,count 手指的個數
final int count = event.getPointerCount();
...
// streamComplete表示當前事件留是否完成
final boolean streamComplete = action == MotionEvent.ACTION_UP ||
action == MotionEvent.ACTION_CANCEL || anchoredScaleCancelled;
if (action == MotionEvent.ACTION_DOWN || streamComplete) {
// mInProgress表示是否進行縮放,這裡是停掉上一次的縮放呼叫onScaleEnd()
if (mInProgress) {
mListener.onScaleEnd(this);
mInProgress = false;
mInitialSpan = 0;
mAnchoredScaleMode = ANCHORED_SCALE_MODE_NONE;
} else if (inAnchoredScaleMode() && streamComplete) {
mInProgress = false;
mInitialSpan = 0;
mAnchoredScaleMode = ANCHORED_SCALE_MODE_NONE;
}
if (streamComplete) {
return true;
}
}
...
if (inAnchoredScaleMode()) {
...
} else {
// 所有手指的距離相加
for (int i = 0; i < count; i++) {
if (skipIndex == i) continue;
sumX += event.getX(i);
sumY += event.getY(i);
}
// 所有手指的中心點
focusX = sumX / div;
focusY = sumY / div;
}
// Determine average deviation from focal point
float devSumX = 0, devSumY = 0;
for (int i = 0; i < count; i++) {
if (skipIndex == i) continue;
// 所有手指相對於中心點(所有手指的中心點)的距離之和
devSumX += Math.abs(event.getX(i) - focusX);
devSumY += Math.abs(event.getY(i) - focusY);
}
// 所有手指相對於中心點的平均值
final float devX = devSumX / div;
final float devY = devSumY / div;
// *2 相當於是兩個手指之間的距離跨度
final float spanX = devX * 2;
final float spanY = devY * 2;
final float span;
if (inAnchoredScaleMode()) {
span = spanY;
} else {
span = (float) Math.hypot(spanX, spanY);
}
...
final int minSpan = inAnchoredScaleMode() ? mSpanSlop : mMinSpan;
// 回撥onScaleBegin(),返回值表示是否開始縮放
if (!mInProgress && span >= minSpan &&
(wasInProgress || Math.abs(span - mInitialSpan) > mSpanSlop)) {
mPrevSpanX = mCurrSpanX = spanX;
mPrevSpanY = mCurrSpanY = spanY;
mPrevSpan = mCurrSpan = span;
mPrevTime = mCurrTime;
mInProgress = mListener.onScaleBegin(this);
}
// 回撥onScale(),如果onScale()返回true,則重新儲存mPrevSpanX,mPrevSpanY,mPrevSpan,mPrevTime
if (action == MotionEvent.ACTION_MOVE) {
mCurrSpanX = spanX;
mCurrSpanY = spanY;
mCurrSpan = span;
boolean updatePrev = true;
if (mInProgress) {
updatePrev = mListener.onScale(this);
}
if (updatePrev) {
mPrevSpanX = mCurrSpanX;
mPrevSpanY = mCurrSpanY;
mPrevSpan = mCurrSpan;
mPrevTime = mCurrTime;
}
}
return true;
}
我onTouchEvent()裡面的一些關鍵的地方,直接註釋在程式碼裡面了。
onTouchEvent()函式裡面,我們要注意onScale()的返回值為true的時候mPrevSpanX,mPrevSpanY,mPrevSpan,mPrevTime這些才會改變。
我再看下縮放過程中的縮放因子是怎麼計算到的。getScaleFactor()函式。
public float getScaleFactor() {
...
return mPrevSpan > 0 ? mCurrSpan / mPrevSpan : 1;
}
簡單吧,用當前兩個手指之間的跨度除以上一次記錄的兩個手指之間的跨度。同時我們也注意到上面講到的onTouchEvent()函式裡面onScale()返回true的時候mPrevSpan才會重新賦值。什麼意思,比如我們兩個手指放在螢幕上,手指慢慢的拉開。假設回撥過程中我們onScale()函式每次返回的是true,每次onScale()之後getScaleFactor()會重新去計算縮放因子,但是如果onScale()函式返回的是false,getScaleFactor()的返回值是一直增大的。