1. 程式人生 > >Android GestureDetector ScaleGestureDetector

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()的返回值是一直增大的。


       本文中對應的例項下載地址