上一篇主要讲了onTouch和onTouchEvent区别:
1、优先判断onTouch要不要执行
2、如果onTouch执行,返回ture则消费了事件,onTouchEvent不再执行
3、onTouch默认是null的,所以系统源代码 是在onTouchEvent里面识别和处理 点击,滑动,长按等事件的。
以上,是分析 View中dispatchTouchEvent方法的源码得等的结论
这次,看看View的onTouchEvent 里面都做了些什么事情。
Android事件分发机制完全解析,带你从源码的角度彻底理解(上) 中分析的源码是老一点版本的源码,我的是api22中的源码
一、View的onTouchEvent源码分析
public boolean onTouchEvent(MotionEvent event) {
final float x = event.getX();
final float y = event.getY();
final int viewFlags = mViewFlags;
//判断控件是否是 不可用 DISABLED 的,若果是,就返回一个结果。
//return语句判断了是否是CLICKABLE 或者 LONG_CLICKABLE的,如果是这两者,返回true,消耗掉了事件。换句话说,如果View不可用,并且 View可点击或者可长按,则消费掉事件,
//这样会 不响应click或者 long click事件。
if ((viewFlags & ENABLED_MASK) == DISABLED) {
if (event.getAction() == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
// 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));
}
//如果当前View设置了TouchDelegate 对象mTouchDelegate,则执行mTouchDelegate 的onTouchEvent事件,根据mTouchDelegate的返回结果,决定要不要继续执行自己的onTouchEvent逻辑。
//Delegate 是代理的意思,简单来说,ViewA 代理了ViewB
//如果是点击事件,你点击了ViewA ,就和点击了ViewB一样,ViewA 走的都是ViewB的代码判断逻辑。
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
//View是可点击或可长按的状态。判断为真
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
//View是可点击或可长按的状态,进入了switch判断
switch (event.getAction()) {
case MotionEvent.ACTION_UP:
boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
//判断了按压press状态和焦点状态,
if ((mPrivateFlags & PFLAG_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 (prepressed) {
// The button is being released before we actually
// showed it as pressed. Make it show the pressed
// state now (before scheduling the click) to ensure
// the user sees it.
setPressed(true, x, y);
}
//如果只是个tap,非长按事件移除了removeLongPressCallback也就是对于长按事件的检查线程。
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();
}
//没有直接执行点击performClick ,而是使用post runable的方式执行 performClick
if (!post(mPerformClick)) {
performClick();
}
}
}
if (mUnsetPressedState == null) {
mUnsetPressedState = new UnsetPressedState();
}
if (prepressed) {
postDelayed(mUnsetPressedState,
ViewConfiguration.getPressedStateDuration());
} else if (!post(mUnsetPressedState)) {
// If the post failed, unpress right now
mUnsetPressedState.run();
}
removeTapCallback();
}
break;
case MotionEvent.ACTION_DOWN:
//先把执行长按的标记mHasPerformedLongPress 设置为false
mHasPerformedLongPress = false;
if (performButtonActionOnTouchDown(event)) {
break;
}
// Walk up the hierarchy to determine if we're inside a scrolling container.
//判断了,是否是在 一个可滑动的容器之内
boolean isInScrollingContainer = isInScrollingContainer();
// For views inside a scrolling container, delay the pressed feedback for
// a short period in case this is a scroll.
//如果是在一个可滑动的容器内:
if (isInScrollingContainer) {
mPrivateFlags |= PFLAG_PREPRESSED;
if (mPendingCheckForTap == null) {
mPendingCheckForTap = new CheckForTap();
}
mPendingCheckForTap.x = event.getX();
mPendingCheckForTap.y = event.getY();
//为了防止这个是滑动的操作,postDelayed 延迟了一小段时间按下事件的检查反馈,延迟事件 getTapTimeout()是100毫秒
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
} else {
// Not inside a scrolling container, so show the feedback right away
//没有在一个可滑动的容器内:设置按下状态和位置,马上显示反馈结果(延迟时间为0)
setPressed(true, x, y);
checkForLongClick(0);
}
break;
case MotionEvent.ACTION_CANCEL:
setPressed(false);
removeTapCallback();
removeLongPressCallback();
break;
case MotionEvent.ACTION_MOVE:
//记录位置变化,传递给子view
drawableHotspotChanged(x, y);
// Be lenient about moving outside of buttons
//判断了是否移动到了View以外,如果仍然在View区域内,移除了点击和长按的检查线程
if (!pointInView(x, y, mTouchSlop)) {
// Outside button
removeTapCallback();
if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
// Remove any future long press/tap checks
removeLongPressCallback();
setPressed(false);
}
}
break;
}
//View是可点击或可长按的状态,if为真.
//可以看到,整个if的最终返回值是true代表处理了此次事件。
//也就是说,事件分发到我这个View的时候,如果我这个View是可点击或者可长按的,我就会消费此次事件,停止分发
return true;
}
//如果View是不可点击状态,整个onTouchEvent 会返回false。
//也就是,我这个View是不可点击也不可长按的,那么我这个View不关心发生了什么事情,我也不做处理。
return false;
}
分部分分析和解释:
1、第一个if
if ((viewFlags & ENABLED_MASK) == DISABLED) {
if (event.getAction() == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
// 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));
}
判断控件是否是 不可用 DISABLED 的,若果是,就返回一个结果。
return语句判断了是否是CLICKABLE 或者 LONG_CLICKABLE的,如果是这两者,返回true,消耗掉了事件。换句话说,如果View不可用,并且 View可点击或者可长按,则消费掉事件,
这样会 不响应click或者 long click事件。
2.第二个if,对于TouchDelegate的判断。
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
如果当前View设置了TouchDelegate 对象mTouchDelegate,则执行mTouchDelegate 的onTouchEvent事件,根据mTouchDelegate的返回结果,决定要不要继续执行自己的onTouchEvent逻辑。TouchDelegate 的使用可以看这里
Delegate 是代理的意思,简单来说,ViewA 代理了ViewB
如果是点击事件,你点击了ViewA ,就和点击了ViewB一样,ViewA 走的都是ViewB的代码判断逻辑,并返回结果,根据返回结果判断是否消费事件。
3、第三个大的 if判断是对View 的点击状态进行的判断
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE))
代码运行到这里,View一定是Enable的状态,此时又进行了,是否可点击状态的判断
A、先分析 if为false的情况,可以看到,如果View是不可点击状态,整个onTouchEvent 会返回false。也就是,我这个View是不可点击也不可长按的,那么我这个View不关心发生了什么事情,我也不做处理。
B、if为真,View是可点击或可长按的状态。
可以看到,整个if的最终返回值是true代表处理了此次事件。
也就是说,事件分发到我这个View的时候,如果我这个View是可点击或者可长按的,我就会消费此次事件,停止分发
C、if为true,进入了switch判断。
①case MotionEvent.ACTION_UP:
判断了按压press状态和焦点状态,如果只是个tap,移除了removeLongPressCallback也就是对于长按事件的检查线程。
没有直接执行点击performClick ,而是使用post runable的方式执行 performClick
②case MotionEvent.ACTION_DOWN:
先把执行长按的标记mHasPerformedLongPress 设置为false
// Walk up the hierarchy to determine if we're inside a scrolling container.
boolean isInScrollingContainer = isInScrollingContainer();
判断了,是否是在 一个可滑动的容器之内
如果是在一个可滑动的容器内:
/ For views inside a scrolling container, delay the pressed feedback for
// a short period in case this is a scroll.
if (isInScrollingContainer) {
mPrivateFlags |= PFLAG_PREPRESSED;
if (mPendingCheckForTap == null) {
mPendingCheckForTap = new CheckForTap();
}
mPendingCheckForTap.x = event.getX();
mPendingCheckForTap.y = event.getY();
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
}
为了防止这个是滑动的操作,postDelayed 延迟了一小段时间按下事件的检查反馈,延迟事件 getTapTimeout()是100毫秒
没有在一个可滑动的容器内:
else {
// Not inside a scrolling container, so show the feedback right away
setPressed(true, x, y);
checkForLongClick(0);
}
设置按下状态和位置,马上显示反馈结果(延迟时间为0)
③case MotionEvent.ACTION_MOVE:
drawableHotspotChanged(x, y);记录位置变化,传递给子view
// Be lenient about moving outside of buttons
if (!pointInView(x, y, mTouchSlop)) {
// Outside button
removeTapCallback();
if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
// Remove any future long press/tap checks
removeLongPressCallback();
setPressed(false);
}
}
判断了是否移动到了View以外,如果仍然在View区域内,移除了点击和长按的检查线程
以上就是View的onTouchEvent的源码分析,有些乱,但大体都解释到了
二、View的onTouchEvent的总结
View的onTouchEvent首先判断了View是否是可用的。
A.不可用但可点击,返回true,消费了事件
B.不可用但可长按,返回true,消费了事件View是可用的状态,进行了View是否设置了TouchDelegate 的判断
A. 如果设置了TouchDelegate ,则执行TouchDelegate 对象的onTouchEvent方法,根据返回值决定要不要继续执行- View 是可用状态,可点击或者可长按的状态,对MotionEvent对象进行判断。
A. 在MotionEvent.ACTION_UP 动作中,进行是长按还是点击的判断,并执行点击performClick()
B. MotionEvent.ACTION_DOWN动作中,判断了是否是一个可滑动容器
C.MotionEvent.ACTION_MOVE动作中,判断了是否仍在View区域内
三、performClick()代码逻辑
/**
* Call this view's OnClickListener, if it is defined. Performs all normal
* actions associated with clicking: reporting accessibility event, playing
* a sound, etc.
*
* @return True there was an assigned OnClickListener that was called, false
* otherwise is returned.
*/
public boolean performClick() {
final boolean result;
final ListenerInfo li = mListenerInfo;
if (li != null && li.mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
li.mOnClickListener.onClick(this);
result = true;
} else {
result = false;
}
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
return result;
}
主要是判断li.mOnClickListener不为null,然后执行setOnClickListener 设置的点击事件。
public void setOnClickListener(@Nullable OnClickListener l) {
if (!isClickable()) {
setClickable(true);
}
getListenerInfo().mOnClickListener = l;
}
而且可以看到,在setOnClickListener 里面,已经把View设置为Clickable可点击的了。
以上就是View的onTouchEvent执行的逻辑。