Android源码阅读1————GestureDetectory
#Android源码阅读1————GestureDetectory
一.前言
目前接触Android也差不多一年了,一直处于知其然不知其所以然,所以也是希望从源码的角度出发,理解更多关于Android的知识。
这个系列我希望可以一直写下去,增强我对Android的理解。
开头的第一篇我选择了一个不是很长的源码,GestureDetector手势控制器来开始阅读。
参考资料:Android手势检测——GestureDetector全面分析
二. GestureDetectory的作用
关于GestureDetectory的作用及使用,我的上一篇博客中有讲到
Android艺术开发探索学习笔记2————初识View
这里我就不再赘述了。
三.接口
在GestureDetector有4个接口
接口中每个方法的作用在上一篇博客中也有讲到过。
四.初始化处理
构造方法
//构造方法共有4个,但底层调用都是下面那一个
public GestureDetector(Context context, OnGestureListener listener,Handler handler) {
//设置handler
if (handler != null) {
mHandler = new GestureHandler(handler);
} else {
mHandler = new GestureHandler();
}
//设置listener
mListener = listener;
if (listener instanceof OnDoubleTapListener) {
setOnDoubleTapListener((OnDoubleTapListener) listener);
}
if (listener instanceof OnContextClickListener) {
setContextClickListener((OnContextClickListener) listener);
}
init(context);
}
GestureHandler 方法
private class GestureHandler extends Handler {
GestureHandler() {
super();
}
GestureHandler(Handler handler) {
super(handler.getLooper());
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case SHOW_PRESS:
//手指轻触屏幕的一瞬间,尚未松开,调用接口中的onShowPress
mListener.onShowPress(mCurrentDownEvent);
break;
case LONG_PRESS:
//长按
dispatchLongPress();
break;
case TAP:
// If the user's finger is still down, do not count it as a tap
//这里控制SingleTapConfirmed的回调,
if (mDoubleTapListener != null) {
if (!mStillDown) {
//回调onSingleTapConfirmed
mDoubleTapListener.onSingleTapConfirmed(mCurrentDownEvent);
} else {
//如果处理Message的时候还没松开,就设置mDeferConfirmSingleTap为true,在UP事件的时候调用SingleTapConfirme
mDeferConfirmSingleTap = true;
}
}
break;
default:
throw new RuntimeException("Unknown message " + msg); //never
}
}
}
//长按处理
private void dispatchLongPress() {
mHandler.removeMessages(TAP);
mDeferConfirmSingleTap = false;
mInLongPress = true;
mListener.onLongPress(mCurrentDownEvent);
}
setOnDoubleTapListener&setContextClickListener方法
public void setOnDoubleTapListener(OnDoubleTapListener onDoubleTapListener) {
mDoubleTapListener = onDoubleTapListener;
}
public void setContextClickListener(OnContextClickListener onContextClickListener) {
mContextClickListener = onContextClickListener;
}
init方法
private void init(Context context) {
if (mListener == null) {
throw new NullPointerException("OnGestureListener must not be null");
}
mIsLongpressEnabled = true;
// 获取设置,边界距离,用于判断是否属于滑动,是否属于双击。
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;
}
五. 输入处理
初始化完之后,就是看它如何处理输入。
1.onTouchEvent
public boolean onTouchEvent(MotionEvent ev) {
//检查事件输入的一致性,log出来一致性的信息,如:有事件只有up没有down
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(ev, 0);
}
final int action = ev.getAction();
//开始速度检测
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
}
mVelocityTracker.addMovement(ev);
//检测是否非主要指针抬起动作(如果是多点触摸)
final boolean pointerUp =
(action & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_POINTER_UP;
final int skipIndex = pointerUp ? ev.getActionIndex() : -1;
final boolean isGeneratedGesture =
(ev.getFlags() & MotionEvent.FLAG_IS_GENERATED_GESTURE) != 0;
// Determine focal point
//是非主要指针抬起动作会跳过
float sumX = 0, sumY = 0;
final int count = ev.getPointerCount();
//把所有还在触摸顶点手指的位置x,y加起来,求平均值。
for (int i = 0; i < count; i++) {
if (skipIndex == i) continue;
sumX += ev.getX(i);
sumY += ev.getY(i);
}
final int div = pointerUp ? count - 1 : count;
final float focusX = sumX / div;
final float focusY = sumY / div;
boolean handled = false;
switch (action & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_POINTER_DOWN:
//...多点触摸动作
break;
case MotionEvent.ACTION_POINTER_UP:
//...多点离开动作
break;
case MotionEvent.ACTION_DOWN:
//...单点触摸动作
break;
case MotionEvent.ACTION_MOVE:
//...触摸点移动动作
break;
case MotionEvent.ACTION_UP:
//...单点触摸离开动作
break;
case MotionEvent.ACTION_CANCEL://触摸动作取消
cancel();
break;
}
//对未被处理的事件进行一次一致性检测
if (!handled && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(ev, 0);
}
return handled;
}
onTouchEvent()的主要思路就是先对输入事件做出统一处理,提取一些共有的信息,如多个点同时触摸时候的中心焦点和滑动速度等,然后根据事件的分类做出相应的处理。
2.DOWN事件处理
case MotionEvent.ACTION_DOWN:
if (mDoubleTapListener != null) {
//处理双击
//取消TAP事件
boolean hadTapMessage = mHandler.hasMessages(TAP);
if (hadTapMessage) mHandler.removeMessages(TAP);
//判断是否是双击事件,isConsideredDoubleTap函数在下
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
//延时发出单击事件,如果到了时间(300ms)还没有取消的话就确认是TAP事件了
mHandler.sendEmptyMessageDelayed(TAP, DOUBLE_TAP_TIMEOUT);
}
}
mDownFocusX = mLastFocusX = focusX;
mDownFocusY = mLastFocusY = focusY;
//重置CurrentDownEvent
if (mCurrentDownEvent != null) {
mCurrentDownEvent.recycle();
}
mCurrentDownEvent = MotionEvent.obtain(ev);
mAlwaysInTapRegion = true;
mAlwaysInBiggerTapRegion = true;
mStillDown = true;
mInLongPress = false;
mDeferConfirmSingleTap = false;
//处理长按
//先检测用户是否允许检测长按
if (mIsLongpressEnabled) {
mHandler.removeMessages(LONG_PRESS);
//延时发送长按事件
mHandler.sendEmptyMessageAtTime(LONG_PRESS,
mCurrentDownEvent.getDownTime() + LONGPRESS_TIMEOUT);
}
//延时发送showPress事件
mHandler.sendEmptyMessageAtTime(SHOW_PRESS,
mCurrentDownEvent.getDownTime() + TAP_TIMEOUT);
handled |= mListener.onDown(ev);
break;
//判断第二次点击是否是有效点击
private boolean isConsideredDoubleTap(MotionEvent firstDown, MotionEvent firstUp,
MotionEvent secondDown) {
if (!mAlwaysInBiggerTapRegion) {
return false;
}
//判断两次点击事件间隔是否符合要求
final long deltaTime = secondDown.getEventTime() - firstUp.getEventTime();
if (deltaTime > DOUBLE_TAP_TIMEOUT || deltaTime < DOUBLE_TAP_MIN_TIME) {
return false;
}
//判断两次点击距离是否在有效范围内
int deltaX = (int) firstDown.getX() - (int) secondDown.getX();
int deltaY = (int) firstDown.getY() - (int) secondDown.getY();
//MotionEvent.FLAG_IS_GENERATED_GESTURE标识表示GestureDector不使用任何触摸
final boolean isGeneratedGesture =
(firstDown.getFlags() & MotionEvent.FLAG_IS_GENERATED_GESTURE) != 0;
int slopSquare = isGeneratedGesture ? 0 : mDoubleTapSlopSquare;
//判断第二次点击是否在附近,在附近才被认为是双击
return (deltaX * deltaX + deltaY * deltaY < slopSquare);
}
在DOWN事件中涉及到
- 处理单击事件,如果收到一次Down事件,并且前端时间也没有DOWN事件的话,会发送一个延时的TAP信息,而一段时间(300ms)之后没有取消的话,就执行GestureHandler里面的TAP单击确认操作
- 处理双击事件,如果前面有一次DOWN事件,而且也符合isConsideredDoubleTap()的条件(第一次点击后没有移动超出范围,第二次点击也在附近),就可以确认双击,执行onDoubleTap()和onDoubleTapEvent()的回调。
- 处理长按事件:先看用户是否允许检查长按,然后发送一个延时LONG_PRESS信息,如果到时候还没取消,就回调长按方法。
- 处理showPress判断:这个和长按差不多,只是短一些。
3. MOVE事件处理
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;
//mTouchSlopSquare是一个距离的平方,表示滑动的时候,手的移动要大于这个距离才认为是Scroll事件
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)) {
//后续的Scroll移动,前面的是进入Scroll移动
handled = mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY);
mLastFocusX = focusX;
mLastFocusY = focusY;
}
break;
MOVE事件涉及到
- onDoubleTapEvent() 回调:只要确认是双击之后,mIsDoubleTapping为true,除了长按,后面的MOVE事件都会只回调onDoubleTapEvent().
- onScroll回调:当MOVE不是长按时,不是DoubleTapEvent之后,当移动距离大于移动距离之后,就会进入Scroll模式,然后两个MOVE事件的位移距离scrollX或者scroolY大于1px,就会调用onScroll。
4.UP事件的处理
case MotionEvent.ACTION_UP:
mStillDown = false;
MotionEvent currentUpEvent = MotionEvent.obtain(ev);
if (mIsDoubleTapping) {
// Finally, give the up event of the double-tap
//双击事件,回调onDoubleTapEvent
handled |= mDoubleTapListener.onDoubleTapEvent(ev);
} else if (mInLongPress) {
//长按结束
mHandler.removeMessages(TAP);
mInLongPress = false;
} else if (mAlwaysInTapRegion && !mIgnoreNextUpEvent) {
handled = mListener.onSingleTapUp(ev);
//处理单击确认,具体逻辑在GestureHandler如何处理TAP事件
if (mDeferConfirmSingleTap && mDoubleTapListener != null) {
mDoubleTapListener.onSingleTapConfirmed(ev);
}
} else if (!mIgnoreNextUpEvent) {
//处理Fling,如果速度符合要求,就回调fling
// 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);
}
}
//重置mPreviousUpEvent
if (mPreviousUpEvent != null) {
mPreviousUpEvent.recycle();
}
// Hold the event we obtained above - listeners may have changed the original.
mPreviousUpEvent = currentUpEvent;
//回收mVelocityTracker
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;
UP事件涉及到:
- onDoubleTapEvent()回调:只要确认是双击之后,mIsDoubleTapping为true,除了长按,后面的MOVE事件都会只回调onDoubleTapEvent()。
- onSingleTapUp()回调:DOWN事件之后没有MOVE,或者MOVE的距离没有超出范围,mAlwaysInTapRegion才不会变成false,回调onSingleTapUp()。
- onSingleTapConfirmed()回调
5.多点事件的处理
case MotionEvent.ACTION_POINTER_DOWN:
mDownFocusX = mLastFocusX = focusX;
mDownFocusY = mLastFocusY = focusY;
// Cancel long press and taps
//如果有多个手指按下,取消长按和点击计时
cancelTaps();
break;
case MotionEvent.ACTION_POINTER_UP:
mDownFocusX = mLastFocusX = focusX;
mDownFocusY = mLastFocusY = focusY;
// Check the dot product of current velocities.
// If the pointer that left was opposing another velocity vector, clear.
//计算速度
mVelocityTracker.computeCurrentVelocity(1000, mMaximumFlingVelocity);
final int upIndex = ev.getActionIndex();
final int id1 = ev.getPointerId(upIndex);
final float x1 = mVelocityTracker.getXVelocity(id1);
final float y1 = mVelocityTracker.getYVelocity(id1);
//如果剩下的手指速度方向和抬起那根手指速度相反方向,就说明不是fling,清空速度监听
for (int i = 0; i < count; i++) {
if (i == upIndex) continue;
final int id2 = ev.getPointerId(i);
final float x = x1 * mVelocityTracker.getXVelocity(id2);
final float y = y1 * mVelocityTracker.getYVelocity(id2);
//如果速度相反
final float dot = x + y;
if (dot < 0) {
mVelocityTracker.clear();
break;
}
}
break;
6. ContextClick的处理
//ContextClick处理事件
public boolean onGenericMotionEvent(MotionEvent ev) {
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onGenericMotionEvent(ev, 0);
}
final int actionButton = ev.getActionButton();
switch (ev.getActionMasked()) {
case MotionEvent.ACTION_BUTTON_PRESS:
//按下触控笔首选按键或者鼠标右键
if (mContextClickListener != null && !mInContextClick && !mInLongPress
&& (actionButton == MotionEvent.BUTTON_STYLUS_PRIMARY
|| actionButton == MotionEvent.BUTTON_SECONDARY)) {
if (mContextClickListener.onContextClick(ev)) {
mInContextClick = true;
mHandler.removeMessages(LONG_PRESS);
mHandler.removeMessages(TAP);
return true;
}
}
break;
case MotionEvent.ACTION_BUTTON_RELEASE:
if (mInContextClick && (actionButton == MotionEvent.BUTTON_STYLUS_PRIMARY
|| actionButton == MotionEvent.BUTTON_SECONDARY)) {
//无视下一个UP事件,因为它是由鼠标右键或者触控笔键带起的
mInContextClick = false;
mIgnoreNextUpEvent = true;
}
break;
}
return false;
}
六.后记
第一次分析源码,还是有很多的不足,在这其中也是百度了很多,也对其他的博客参考了很多。发现自己阅读代码的能力还是有些薄弱,希望自己可以把阅读源码这个习惯坚持下去,让自己的阅读代码的能卡里更近一层,对Android的了解更加深入.
上一篇: array_merge合并有关问题