其實在咱們平常的編程中,對於縮放手勢的使用並非很常常,這一手勢主要是用在圖片瀏覽方面,好比下方例子。可是(敲重點),做爲 Android 入門的基礎來講,學習 ScaleGestureDetector 的使用,算是不得不過的一道坎,好在 ScaleGestureDetector 使用起來很是簡單,就是源碼分析上得花些功夫。java
本文首先將簡單的介紹下 ScaleGestureDetector 的使用,在重點給你們分析下源碼(因爲源碼方面是我本身的理解,可能有誤差,但願各位大佬能在評論區指出,萬分感謝~)編程
ScaleGestureDetector 包括一個監聽器,以及它全部方法的空實現:app
名稱 | 用途 |
---|---|
ScaleGestureDetector | 縮放手勢的監聽器 |
SimpleOnScaleGestureListener | 該監聽器的空實現,在其中重寫方法 |
名稱 | 用途 |
---|---|
onScaleBegin | 當 >= 2 個手指碰觸屏幕時調用,若返回 false 則忽略改事件調用 |
onScale | 滑動(縮放)過程當中調用,若成功處理,則用戶返回 true,監聽器繼續記錄下一個縮放等動做,若爲 false 代表數據未處理,則監聽器繼續積累 |
onScaleEnd | 所有手指離開屏幕,結束監聽 |
一般狀況下,手勢監聽會結合自定義 View 來說,這裏我給出一個最簡單的使用,具體的使用實例,之後再結合自定義 View 講講。ide
private void iniScaleGestureListener(){ mListener = new ScaleGestureDetector.SimpleOnScaleGestureListener(){ @Override public boolean onScaleBegin(ScaleGestureDetector detector) { return super.onScaleBegin(detector); } @Override public boolean onScale(ScaleGestureDetector detector) { MyLog.d("X:" + detector.getFocusX()); MyLog.d("Y:" + detector.getFocusY()); MyLog.d("scale:" + detector.getScaleFactor()); return super.onScale(detector); } @Override public void onScaleEnd(ScaleGestureDetector detector) { super.onScaleEnd(detector); } }; detector = new ScaleGestureDetector(getContext(), mListener); } @Override public boolean onTouchEvent(MotionEvent event) { detector.onTouchEvent(event); return true; }
ScaleGestureDetector 在具體項目的使用有點複雜,我打算過段時間結合自定義 View 寫一篇用來總結,因此這篇咱們就先了解下 ScaleGestureDetector 的基本使用。源碼分析
好了,如今咱們進入本章重點,ScaleGestureDetector 源碼分析,敲黑板敲黑板。首先,咱們打開 ScaleGestureDetector 的源碼能夠看到,幾乎全部的代碼都集中在了 onTouchEvent 這個方法上,因此在這裏,我就主要給你們介紹這個方法的實現。學習
if (mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onTouchEvent(event, 0); } mCurrTime = event.getEventTime(); final int action = event.getActionMasked(); // Forward the event to check for double tap gesture if (mQuickScaleEnabled) { mGestureDetector.onTouchEvent(event); } final int count = event.getPointerCount(); final boolean isStylusButtonDown = (event.getButtonState() & MotionEvent.BUTTON_STYLUS_PRIMARY) != 0;
if (mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onTouchEvent(event, 0); }
mCurrTime = event.getEventTime();
final int action = event.getActionMasked();
if (mQuickScaleEnabled) { mGestureDetector.onTouchEvent(event); }
final int count = event.getPointerCount();
這個主要是因爲判斷手寫筆是否按下
因爲咱們不多處理手寫筆,因此這裏不作過多說明ui
final boolean isStylusButtonDown = (event.getButtonState() & MotionEvent.BUTTON_STYLUS_PRIMARY) != 0;
## 第二部分:處理與手勢變化this
用戶的縮放手勢不老是必定的,就是說對於用戶而言,隨時可能有手指碰觸或離開屏幕,這就使得縮放中心的(焦點)隨時可能發生變化,這部分主要是用來處理這一變化,並作出響應。google
final boolean anchoredScaleCancelled = mAnchoredScaleMode == ANCHORED_SCALE_MODE_STYLUS && !isStylusButtonDown; final boolean streamComplete = action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL || anchoredScaleCancelled; // 若是發生了上面這種小動做,或者說有一手指離開了屏幕,進行調用 if (action == MotionEvent.ACTION_DOWN || streamComplete) { // Reset any scale in progress with the listener. // If it's an ACTION_DOWN we're beginning a new event stream. // This means the app probably didn't give us all the events. Shame on it. 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; } }
### anchoredScaleCancelledspa
final boolean anchoredScaleCancelled = mAnchoredScaleMode == ANCHORED_SCALE_MODE_STYLUS && !isStylusButtonDown;
final boolean streamComplete = action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL || anchoredScaleCancelled;
if (action == MotionEvent.ACTION_DOWN || streamComplete) {...}
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; }
總結: 能夠看到,當觸發 down 或者觸發 up,cancel 時,若是以前處於縮放計算的狀態,會將其狀態重置, 並調用 onScaleEnd 方法。
if (!mInProgress && mStylusScaleEnabled && !inAnchoredScaleMode() && !streamComplete && isStylusButtonDown) { // Start of a button scale gesture mAnchoredScaleStartX = event.getX(); mAnchoredScaleStartY = event.getY(); mAnchoredScaleMode = ANCHORED_SCALE_MODE_STYLUS; mInitialSpan = 0; }
mAnchoredScaleStartX = event.getX(); mAnchoredScaleStartY = event.getY();
mAnchoredScaleMode = ANCHORED_SCALE_MODE_STYLUS;
final boolean configChanged = action == MotionEvent.ACTION_DOWN || action == MotionEvent.ACTION_POINTER_UP || action == MotionEvent.ACTION_POINTER_DOWN || anchoredScaleCancelled; final boolean pointerUp = action == MotionEvent.ACTION_POINTER_UP; final int skipIndex = pointerUp ? event.getActionIndex() : -1; // Determine focal point float sumX = 0, sumY = 0; final int div = pointerUp ? count - 1 : count; final float focusX; final float focusY; if (inAnchoredScaleMode()) { // In anchored scale mode, the focal pt is always where the double tap // or button down gesture started focusX = mAnchoredScaleStartX; focusY = mAnchoredScaleStartY; if (event.getY() < focusY) { mEventBeforeOrAboveStartingGestureEvent = true; } else { mEventBeforeOrAboveStartingGestureEvent = false; } } 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; }
final boolean configChanged = action == MotionEvent.ACTION_DOWN || action == MotionEvent.ACTION_POINTER_UP || action == MotionEvent.ACTION_POINTER_DOWN || anchoredScaleCancelled;
final boolean pointerUp = action == MotionEvent.ACTION_POINTER_UP;
final int skipIndex = pointerUp ? event.getActionIndex() : -1;
// Determine focal point float sumX = 0, sumY = 0; // 若是是擡起手指,則當前手指數減1,不然不變 final int div = pointerUp ? count - 1 : count; final float focusX; final float focusY;
if (inAnchoredScaleMode()) { // In anchored scale mode, the focal pt is always where the double tap // or button down gesture started // 在錨定比例模式中,焦點pt始終是雙擊的位置,或按下手勢開始 focusX = mAnchoredScaleStartX; focusY = mAnchoredScaleStartY; if (event.getY() < focusY) { mEventBeforeOrAboveStartingGestureEvent = true; } else { mEventBeforeOrAboveStartingGestureEvent = false; } } 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 @Google translate float devSumX = 0, devSumY = 0; for (int i = 0; i < count; i++) { if (skipIndex == i) continue; // Convert the resulting diameter into a radius. devSumX += Math.abs(event.getX(i) - focusX); devSumY += Math.abs(event.getY(i) - focusY); } final float devX = devSumX / div; final float devY = devSumY / div; // Span is the average distance between touch points through the focal point; // i.e. the diameter of the circle with a radius of the average deviation from // the focal point. final float spanX = devX * 2; final float spanY = devY * 2; final float span; if (inAnchoredScaleMode()) { span = spanY; } else { span = (float) Math.hypot(spanX, spanY); }
float devSumX = 0, devSumY = 0; for (int i = 0; i < count; i++) { if (skipIndex == i) continue; // Convert the resulting diameter into a radius. devSumX += Math.abs(event.getX(i) - focusX); devSumY += Math.abs(event.getY(i) - focusY); } final float devX = devSumX / div; final float devY = devSumY / div;
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 boolean wasInProgress = mInProgress; mFocusX = focusX; mFocusY = focusY; if (!inAnchoredScaleMode() && mInProgress && (span < mMinSpan || configChanged)) { mListener.onScaleEnd(this); mInProgress = false; mInitialSpan = span; } if (configChanged) { mPrevSpanX = mCurrSpanX = spanX; mPrevSpanY = mCurrSpanY = spanY; mInitialSpan = mPrevSpan = mCurrSpan = span; }
final int minSpan = inAnchoredScaleMode() ? mSpanSlop : mMinSpan; 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); }
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; } }
if (mInProgress) { updatePrev = mListener.onScale(this); } if (updatePrev) { mPrevSpanX = mCurrSpanX; mPrevSpanY = mCurrSpanY; mPrevSpan = mCurrSpan; mPrevTime = mCurrTime; }
我要講的全部內容,到這裏就徹底結束了
因爲源碼是按照我本身的理解來說的,因此不免會有一些出入
但願你們能在評論區中幫我指出,謝謝~ 🙏