我们在开发过程中经常会用到控件的 setOnTouchListener,setOnClickListener来进行点击、触摸、滑动、长按等事件的处理。那么从手指点到屏幕,到最终这个触摸事件是怎么被认定为点击,或者长按等操作的识别呢,下面通过源码的方式来看一下。
总体我们分两篇文章来讲分别为View的事件分发,ViewGroup的事件分发。我们先来看View的事件分发。
ViewGroup事件分发机制
Android事件分发机制之ViewGroup事件分发
当我们点击任何一个view的时候,最终都会调用该view的dispatchTouchEvent方法,比如点击一个button但是他的源码中没有该方法,那就去他的父类去找,最终在view类中找到该方法。在较新的的sdk中dispatchTouchEvent比较复杂,我们以sdk 22版本的方法来看,因为事件分发机制的主体逻辑都是一样,老版本的代码更容易理解。新版本上加上了一些对屏幕的判断和事件丢弃功能。下面看代码
public boolean dispatchTouchEvent(MotionEvent event) {
if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
mOnTouchListener.onTouch(this, event)) {
return true;
}
return onTouchEvent(event);
}
这段代码中我们可以看这个if中包含三个条件 三个条件都为true的话就会直接返回true从而下面的onTouchEvent方法不会执行。我们挨个看一下
mOnTouchListener 也就是我们给该控件有没有设置OnTouchListener,这个很好理解
(mViewFlags & ENABLED_MASK) == ENABLED 就是这个view的状态是不是可用,一般默认是可用的。
mOnTouchListener.onTouch(this, event) 这个就是调用了 我们给view设置的mOnTouchListener的onTouch方法,如果这个方法返回true 那么整个条件就都是true,直接返回不会执行onTouchEvent方法。如果返回了false那么就会调用下面的 onTouchEvent方法。
我们看一下onTouchEvent方法
public boolean onTouchEvent(MotionEvent event) {
final int viewFlags = mViewFlags;
if ((viewFlags & ENABLED_MASK) == DISABLED) {
// A disabled view that is clickable still consumes the touch
// events, it just doesn't respond to them.
return (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));
}
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
switch (event.getAction()) {
case MotionEvent.ACTION_UP:
boolean prepressed = (mPrivateFlags & PREPRESSED) != 0;
if ((mPrivateFlags & PRESSED) != 0 || prepressed) {
// take focus if we don't have it already and we should in
// touch mode.
boolean focusTaken = false;
if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
focusTaken = requestFocus();
}
if (!mHasPerformedLongPress) {
// This is a tap, so remove the longpress check
removeLongPressCallback();
// Only perform take click actions if we were in the pressed state
if (!focusTaken) {
// Use a Runnable and post this rather than calling
// performClick directly. This lets other visual state
// of the view update before click actions start.
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
performClick();
}
}
}
if (mUnsetPressedState == null) {
mUnsetPressedState = new UnsetPressedState();
}
if (prepressed) {
mPrivateFlags |= PRESSED;
refreshDrawableState();
postDelayed(mUnsetPressedState,
ViewConfiguration.getPressedStateDuration());
} else if (!post(mUnsetPressedState)) {
// If the post failed, unpress right now
mUnsetPressedState.run();
}
removeTapCallback();
}
break;
case MotionEvent.ACTION_DOWN:
if (mPendingCheckForTap == null) {
mPendingCheckForTap = new CheckForTap();
}
mPrivateFlags |= PREPRESSED;
mHasPerformedLongPress = false;
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
break;
case MotionEvent.ACTION_CANCEL:
mPrivateFlags &= ~PRESSED;
refreshDrawableState();
removeTapCallback();
break;
case MotionEvent.ACTION_MOVE:
final int x = (int) event.getX();
final int y = (int) event.getY();
// Be lenient about moving outside of buttons
int slop = mTouchSlop;
if ((x < 0 - slop) || (x >= getWidth() + slop) ||
(y < 0 - slop) || (y >= getHeight() + slop)) {
// Outside button
removeTapCallback();
if ((mPrivateFlags & PRESSED) != 0) {
// Remove any future long press/tap checks
removeLongPressCallback();
// Need to switch from pressed to not pressed
mPrivateFlags &= ~PRESSED;
refreshDrawableState();
}
}
break;
}
return true;
}
return false;
}
我们手指在点下后首先会出发 ACTION_DOWN 事件,这里会发送一个500ms的延迟事件,这个事件如果被执行的话就会调用我们的mOnLongClickListener.onLongClick方法。但是我们不是所有的操作都会触发这个onLongClick的,
我们来看一下ACTION_UP事件,这里有一个mHasPerformedLongPress 标示标示着这个在ACTION_DOWN 的时候埋下的延迟长点击事件是否执行了,如果为false,则说明现在还没到500ms但是手指已经离开屏幕了 所以这是一个tap事件,则把该延迟事件移除,后面代码会执行到performClick方法,该方法中最终会调用到view的onClick方法。如果为true 则说明长按事件已经执行了 就不会走view的click事件了
这也是 点击跟长按的实现方式。
我们重新屡一下View的事件分发流程
dispatchTouchEvent—>ontouchListener------>onTouchEvent----->onClickListener/onLongClickListener
流程中,如果onTouchListener返回true的话,也就是说这个事件被onTouchLitener消费掉了,后面的onTouchEvent就不会被执行了。
总结
在屡这个事件分发机制的时候 首先我们先只看view的分发,先抛开viewgroup的事件拦截等机制。一定要看源码!一定要看源码!一定要看源码!
下一篇文章,ViewGroup事件分发机制
Android事件分发机制之ViewGroup事件分发