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


       本文中对应的实例下载地址

更多相关文章

  1. Linux下的两种timer方法 (Android(安卓)下NDK开发)
  2. Android(安卓)怎么把异常的StackTrace信息格式化为String
  3. Android(安卓)一般动画Animation和属性动画Animator
  4. 对Android之事件分发机制的理解
  5. Android设置TextView部分文字变色及点击事件
  6. android 通过Intent打开相册并获取选择的图片
  7. android点击事件
  8. 三种解析xml的方式
  9. android 按键处理流程

随机推荐

  1. Android(安卓)Material Design-UI
  2. android自己添加的模块在user模式下不编
  3. Android如何读写CSV文件方法示例
  4. android之IntentFilter的用法_Intent.ACT
  5. unity项目中,需要将文本内容复制到系统剪
  6. Android打开手机相册获取图片路径
  7. Permission Denial: opening provider 隐
  8. Android(安卓)自定义View实现动画形式加
  9. [Android(安卓)Studio]设置Button的圆角
  10. Android(安卓)@IntDef的使用 - 代替枚举