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()的返回值是一直增大的。
本文中对应的实例下载地址