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()的返回值是一直增大的。
本文中对应的实例下载地址
更多相关文章
- Linux下的两种timer方法 (Android(安卓)下NDK开发)
- Android(安卓)怎么把异常的StackTrace信息格式化为String
- Android(安卓)一般动画Animation和属性动画Animator
- 对Android之事件分发机制的理解
- Android设置TextView部分文字变色及点击事件
- android 通过Intent打开相册并获取选择的图片
- android点击事件
- 三种解析xml的方式
- android 按键处理流程