源码
View # dispatchTouchEvent()
public boolean dispatchTouchEvent(MotionEvent event) {
// If the event should be handled by accessibility focus first.
// 如果该事件需要先被处于焦点的View处理
if (event.isTargetAccessibilityFocus()) {
// We don't have focus or no virtual descendant has it, do not handle the event.
// 判断当前View是否就是处于焦点的或是焦点View处于该View的下级。
// boolean isAccessibilityFocusedViewOrHost() {
// return isAccessibilityFocused() || (getViewRootImpl() != null && getViewRootImpl()
// .getAccessibilityFocusedHost() == this);
// }
if (!isAccessibilityFocusedViewOrHost()) {
// 如果不符合上面的判断,就直接返回false,结束此View的分发。
return false;
}
// We have focus and got the event, then use normal event dispatch.
// 如果满足上面的判断,就继续正常的分发过程,设置参数为false,避免下次重复判断。
event.setTargetAccessibilityFocus(false);
}
boolean result = false;
// 如果事件检查对象不为空,就检查这个事件,包括检查事件序列完整性等等。
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(event, 0);
}
// 获取到事件类型
final int actionMasked = event.getActionMasked();
// 如果是ACTION_DOWN
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Defensive cleanup for new gesture
// 如果有滚动条,就停止滚动,并清除滚动条
stopNestedScroll();
}
// public boolean onFilterTouchEventForSecurity(MotionEvent event) {
// //noinspection RedundantIfStatement
// 检查是否需要过滤到该事件,如果该View要求当窗口遮挡时过滤事件,并且事件被遮挡了。
// if ((mViewFlags & FILTER_TOUCHES_WHEN_OBSCURED) != 0
// && (event.getFlags() & MotionEvent.FLAG_WINDOW_IS_OBSCURED) != 0) {
// // Window is obscured, drop this touch.
// 就返回false,表示过滤该事件。
// return false;
// }
// 否则返回true,不过滤。
// return true;
// }
if (onFilterTouchEventForSecurity(event)) {
// 尝试当作操作滚动条的事件处理。
//先判断View是否是可用的并且当前事件是为了操作滚动条的,如果是就先将返回值置为true。
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;
// 尝试onTouch()处理。
// 四个判断
// 1. ListenerInfo不为空
// 2. ListenerInfo的onTouchListener不为空,onTouchListener需要手动设置。
// 3. 该View是enable的,很多View默认为enable
// 4. onTouchListener的onTouch()返回true,这个方法需要看自己怎么实现onTouch()。
// 都满足就先将返回值置为true。
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
// 尝试onTouchEvent()处理。
// 如果经过了上面一系列的判断,result没有被设置为true,就调用onTouchEvent(),看是否能被消耗。
// 如果上面已经设置为true了,那就不去尝试消耗。
if (!result && onTouchEvent(event)) {
// 如果该View消耗了该事件,将返回值置为true。
result = true;
}
}
// 如果经过上面的操作,事件还没有处理。
if (!result && mInputEventConsistencyVerifier != null) {
// 调用onUnhandledEvent(),这个事件的后序检查追踪不需要做了。
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
}
// Clean up after nested scrolls if this is the end of a gesture;
// also cancel it if we tried an ACTION_DOWN but we didn't want the rest
// of the gesture.
// 如果这个事件是ACTION_UP或ACTION_CANCEL或是未处理的ACTION_DOWN
if (actionMasked == MotionEvent.ACTION_UP ||
actionMasked == MotionEvent.ACTION_CANCEL ||
(actionMasked == MotionEvent.ACTION_DOWN && !result)) {
// 就停止滚动。
stopNestedScroll();
}
//将上面设置的result返回,表示是否消耗了该事件。
return result;
}
View # onTouchEvent()
public boolean onTouchEvent(MotionEvent event) {
// 获取时间相对于当前View的坐标
final float x = event.getX();
final float y = event.getY();
final int viewFlags = mViewFlags;
final int action = event.getAction();
// 判断该View是否是可点击的,通过三个标识,CLICKABLE LONG_CLICKABLE CONTEXT_CLICKABLE判断。
final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
// 如果View是disabled的
if ((viewFlags & ENABLED_MASK) == DISABLED) {
// 如果是ACTION_UP时间并且是按下状态,就将状态设回false。
if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
// setPressed()会设置PRESSED状态,
// 并且在需要重新绘制时调用refreshDrawableState()进行重新绘制。
setPressed(false);
}
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
// A disabled view that is clickable still consumes the touch
// events, it just doesn't respond to them.
// 一个disabled的View还是能消耗事件,只是不会做出反馈。
// 不做任何处理,直接返回是否能够点击。只要能点击就默认消耗了该事件。
return clickable;
}
// 判断是否设置了委派
// 大概功能是这样的,假设有两个View,分别是v1,v2,
// 我们可以通过v1的setTouchDelegate(bounds, v2)来委派触摸事件,
// 其中bounds是一个Rect。v1中,落在这个范围的TouchEvent都会传给v2。
if (mTouchDelegate != null) {
// 如果有就尝试交给委派的View来消耗事件。
// 返回true说明委派对象处理了该事件,整个方法也返回true,说明该事件被委托View消耗。
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
// 如果之前两步操作没有消耗该事件,判断该View是可点击的或是可显示ToolTip时
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
// 分事件类型去操作
switch (action) {
case MotionEvent.ACTION_UP:
// ......
break;
case MotionEvent.ACTION_DOWN:
// ......
break;
case MotionEvent.ACTION_CANCEL:
// ......
break;
case MotionEvent.ACTION_MOVE:
// ......
break;
}
// 只要进入了分事件类型处理,就返回true,表示消耗
return true;
}
// 如果既没有在分情况之间消耗返回,也没有进入分情况处理,就返回未消耗
return false;
}
- ACTION_DOWN处理
case MotionEvent.ACTION_DOWN:
// 判断了一下输入源是否为触控屏幕,然后给mPrivateFlags3赋值按下的状态。
if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN) {
mPrivateFlags3 |= PFLAG3_FINGER_DOWN;
}
// 设置没有长按
mHasPerformedLongPress = false;
// 如果该View不可点击
if (!clickable) {
// 检查长按 然后直接跳出
// private void checkForLongClick(int delayOffset, float x, float y) {
// // 可长按或可可加入ToolTip
// if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE || (mViewFlags & TOO
// // 先初始设置长按还没有发生
// mHasPerformedLongPress = false;
// // 初始化检查长按的runnable
// if (mPendingCheckForLongPress == null) {
// mPendingCheckForLongPress = new CheckForLongPress();
// }
// mPendingCheckForLongPress.setAnchor(x, y);
// mPendingCheckForLongPress.rememberWindowAttachCount();
// mPendingCheckForLongPress.rememberPressedState();
// // 加入执行队列,这个runnable执行后如果真的是长按,会将mHasPerformedLongPress置为true
// postDelayed(mPendingCheckForLongPress,
// ViewConfiguration.getLongPressTimeout() - delayOffset);
// }
// }
// CheckForLongPress()中的run
// @Override
// public void run() {
// if ((mOriginalPressedState == isPressed()) && (mParent != null)
// && mOriginalWindowAttachCount == mWindowAttachCount) {
// if (performLongClick(mX, mY)) {
// mHasPerformedLongPress = true;
// }
// }
// }
checkForLongClick(0, x, y);
break;
}
// 这里是处理物理设备点击?
if (performButtonActionOnTouchDown(event)) {
break;
}
// Walk up the hierarchy to determine if we're inside a scrolling container.
// 判断该View是否有一层父类是可滑动的
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) {
// 设置标识位,这个PREPRESSED会在ACTION_UP中用上
mPrivateFlags |= PFLAG_PREPRESSED;
// 检查单击初始化
if (mPendingCheckForTap == null) {
// CheckForTap的run()
// @Override
// public void run() {
// // 设置非预按下
// mPrivateFlags &= ~PFLAG_PREPRESSED;
// // 设置按下状态
// setPressed(true, x, y);
// // 也要检查长按
// checkForLongClick(ViewConfiguration.getTapTimeout(), x, y);
// }
mPendingCheckForTap = new CheckForTap();
}
mPendingCheckForTap.x = event.getX();
mPendingCheckForTap.y = event.getY();
// 将检查任务延时加入执行队列
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
} else {
// Not inside a scrolling container, so show the feedback right away
// 如果不在滑动嵌套内部,就直接不延时去设置按下状态
setPressed(true, x, y);
// 也要检查长按
checkForLongClick(0, x, y);
}
break;
- ACTION_MOVE处理
case MotionEvent.ACTION_MOVE:
// 如果是可点击
if (clickable) {
// 调用去修改热点状态,
// 还能自己实现dispatchDrawableHotspotChanged(x, y)实现分发变化
drawableHotspotChanged(x, y);
}
// Be lenient about moving outside of buttons
// 如果事件是否在View加上最小滑动距离的范围内
if (!pointInView(x, y, mTouchSlop)) {
// Outside button
// Remove any future long press/tap checks
// 将检查的runnable移除
removeTapCallback();
removeLongPressCallback();
// 如果存在按下状态,就取消按下状态
if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
}
break;
- ACTION_CANCEL处理
case MotionEvent.ACTION_CANCEL:
// 如果是可点击的就恢复之前未按下的状态
if (clickable) {
setPressed(false);
}
// 将几个runnable移除 mPendingCheckForTap mPendingCheckForLongPress
// 都是在之前事件中加入的,此事件通知取消,
// 所以哪怕没来得及执行runnable也需要移除
removeTapCallback();
removeLongPressCallback();
// 恢复状态
mInContextButtonPress = false;
mHasPerformedLongPress = false;
mIgnoreNextUpEvent = false;
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
break;
- ACTION_UP处理
case MotionEvent.ACTION_UP:
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
// 如果能有ToolTip
if ((viewFlags & TOOLTIP) == TOOLTIP) {
// 处理ToolTip
// 如果相关ToolTip信息或弹窗为空,就直接返回。
// 如果不空,先移除隐藏ToolTip的runnable,
// 再根据配置时间延时发送,执行隐藏的runnable
// private void handleTooltipUp() {
// if (mTooltipInfo == null || mTooltipInfo.mTooltipPopup == null) {
// return;
// }
// removeCallbacks(mTooltipInfo.mHideTooltipRunnable);
// postDelayed(mTooltipInfo.mHideTooltipRunnable,
// ViewConfiguration.getLongPressTooltipHideTimeout());
// }
handleTooltipUp();
}
// 如果不能点击
if (!clickable) {
// 将两个Callback移除,因为不能点击,它们的runnable就不应该生效。
removeTapCallback();
removeLongPressCallback();
// 相关属性置为false
mInContextButtonPress = false;
mHasPerformedLongPress = false;
mIgnoreNextUpEvent = false;
break;
}
// PREFLAG会在当该View存在一层父View为滑动容器时设置。
boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
// 如果该View处于按下状态或设置了PFLAG_PREFLAG
if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
// take focus if we don't have it already and we should in
// touch mode.
boolean focusTaken = false;
// 判断该View是否为可获得焦点且设置了focusableInTouchMode为true并且还没有获得焦点时
// 设置focusable意思是控件是否能够获得普通焦点,这个可以理解为通过物理键盘,
// 因为Android不仅仅针对手机,有可能在某些设备上存在上下键,
// 如果focusable设置为true,那么键盘上下左右选中,焦点会随之移动。
// focusableInTouchMode意思是控件是否能触摸获得焦点,
// 如果设置为true,点击后首先会让View获得焦点,获取后才出发点击事件。
if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
// 尝试获得焦点,设置focusTaken为true。
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.
// 处理还没来得及显示的按下状态,
// 比如说down事件时延时显示按下状态,到了up时间还没有显示,
// 就设置PFLAG_PRESSED为true,显示按下状态,如果状态更改还需要进行重绘。
// private void setPressed(boolean pressed, float x, float y) {
// if (pressed) {
// drawableHotspotChanged(x, y);
// }
// setPressed(pressed);
// }
// public void setPressed(boolean pressed) {
// // 如果当前标识等于PFLAG_PRESSED,处于按下状态,传参为true则不需要重绘界面,传参false就需要。
// // 如果当前标识不等于PFLAG_PRESSED,未处于按下状态,传参为true就要重绘,false不需要。
// // 也就是更改状态时就需要重绘。
// final boolean needsRefresh = pressed != ((mPrivateFlags & PFLAG_PRESSED) == PFLAG_PRESSED);
// // 根据传参设置mPrivateFlags的值。
// if (pressed) {
// mPrivateFlags |= PFLAG_PRESSED;
// } else {
// mPrivateFlags &= ~PFLAG_PRESSED;
// }
// // 更新界面
// if (needsRefresh) {
// refreshDrawableState();
// }
// // 将按下的状态分发给子View,在这里是空实现。
// dispatchSetPressed(pressed);
// }
setPressed(true, x, y);
}
// 如果没有执行长按并且会不忽略下次ACTION_UP事件
if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
// This is a tap, so remove the longpress check
// 已经ACTION_UP了还没有执行长按,
// 所以移除的长按runnable。
removeLongPressCallback();
// Only perform take click actions if we were in the pressed state
// 当没有通过判断进入上面获取焦点的操作,
// 有可能是因为物理或触摸模式为false,也可能在之前已经获取到了焦点。
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.
// performClick()去调用onClick()方法。
if (mPerformClick == null) {
// private final class PerformClick implements Runnable {
// @Override
// public void run() {
// performClick();
// }
// }
mPerformClick = new PerformClick();
}
// 将构造好的runnable放入消息队列。
// public boolean post(Runnable action) {
// final AttachInfo attachInfo = mAttachInfo;
// if (attachInfo != null) {
// return attachInfo.mHandler.post(action);
// }
// // Postpone the runnable until we know on which thread it needs to run.
// // Assume that the runnable will be successfully placed after attach.
// getRunQueue().post(action);
// return true;
// }
if (!post(mPerformClick)) {
// 如果加入失败,就直接调用performClick()。
performClick();
// public boolean performClick() {
// final boolean result;
// final ListenerInfo li = mListenerInfo;
// // 如果OnClickListener存在
// if (li != null && li.mOnClickListener != null) {
// // 触发声效
// playSoundEffect(SoundEffectConstants.CLICK);
// // 调用onClick()
// li.mOnClickListener.onClick(this);
// result = true;
// } else {
// // 如果成功执行了onClick()就会返回true,否则返回false。
// result = false;
// }
// // 发送一些[辅助功能](https://blog.csdn.net/omnispace/article/details/70598515)通知
// sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
// notifyEnterOrExitForAutoFillIfNeeded(true);
// return result;
// }
}
}
}
// 如果还没有设置回未按下状态
if (mUnsetPressedState == null) {
// 赋值
// private final class UnsetPressedState implements Runnable {
// @Override
// public void run() {
// setPressed(false); // 设置未按下状态
// }
// }
mUnsetPressedState = new UnsetPressedState();
}
// 如果外层有滑动父View
if (prepressed) {
// 延时去发送设置未按下的操作
postDelayed(mUnsetPressedState,
ViewConfiguration.getPressedStateDuration());
} else if (!post(mUnsetPressedState)) {
// 如果没有父类有滑动,那就直接发送设置未按下状态的操作到执行队列。
// 如果没有加入成功,就直接调用方法。
// If the post failed, unpress right now
mUnsetPressedState.run();
}
// 将检察点击的runnable移除。
removeTapCallback();
}
// 不忽略接收下次的ACTION_UP事件
mIgnoreNextUpEvent = false;
break;
框图
dispatchTouchEvent()onTouchEvent()
网友评论