哈哈,搜到了就看一眼吧,没准我们疑惑点一样呢,如果写的不对的地方,欢迎批评指正~
一、耳熟能详的三个方法
1.dispatchTouchEvent()
事件分发处理
2.onInterceptTouchEvent()
是否拦截事件
3.onTouchEvent()
具体事件处理
方法之间的关系
image.png
二、源码分析
一点点来吧,希望不要太枯燥
1.dispatchTouchEvent
2603 // Check for interception.
2604 final boolean intercepted;
2605 if (actionMasked == MotionEvent.ACTION_DOWN
2606 || mFirstTouchTarget != null) {//子元素是否要拦截
2607 final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
2608 if (!disallowIntercept) {//父布局是否要要回拦截
2609 intercepted = onInterceptTouchEvent(ev);
2610 ev.setAction(action); // restore action in case it was changed
2611 } else {
2612 intercepted = false;
2613 }
2614 } else {
2615 // There are no touch targets and this action is not an initial down
2616 // so this view group continues to intercept touches.
2617 intercepted = true;
2618 }
1.1先看2605行的第一个判断
如果对应2617行的情况翻译过来应该是
不是down事件而且mFirstTouchTarget==null 这时候则拦截事件
看到这里估计会先好奇mFirstTouchTarget是个什么东东?
mFirstTouchTarget可以理解成一个标志位(但实际不是哈,他是个单链表结构),代表子类是否已经拦截此事件
所以再次翻译,应该是不是down事件而且子类拦截了事件,所以一个子元素如果拦截了事件,那么下面的一系列事件也由它来处理
那这样说的话,如果我父布局想要回拦截事件呢?
比如:竖向滑动的列表中放一个点击的控件,点击的时候事件给到子元素但是滑动的时候,子元素的点击事件消失了,为什么?事件是怎么传递的?
1.2定位到2608行,是不是看到一个判断,我们来解析上面的疑问
这个判断就是控制父布局要回拦截用的,但是这个值为什么这么算,我们先不管
过程:
第一次 down事件进来,父不拦截,询问子元素
子元素处理了事件,mFirstTouchTarget != null
再次进来,就不再询问,直接返回false
基本过程理解了,我们来看看这个判断值是怎么改变的
定位到Editor(Helper class used by TextView to handle editable text views.)
怎么定位呢,看TextView
@Override
public boolean onTouchEvent(MotionEvent event) {
final int action = event.getActionMasked();
if (mEditor != null) {
mEditor.onTouchEvent(event);
回到Editor的代码中
case MotionEvent.ACTION_UP:
if (!isDragAcceleratorActive()) {
break;
}
updateSelection(event);
// No longer dragging to select text, let the parent intercept events.
6036 mTextView.getParent().requestDisallowInterceptTouchEvent(false);
// No longer the first dragging motion, reset.
resetDragAcceleratorState();
if (mTextView.hasSelection()) {
// Drag selection should not be adjusted by the text classifier.
startSelectionActionModeAsync(mHaventMovedEnoughToStartDrag);
}
break;
看6036行那句英文解释已经很明显了,我就不翻译了。
这句代码将事件还给了父布局
所以,你明白了吗?
还有一部分是用于遍历子元素分发的
2699 if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// Child wants to receive touch within its bounds.
mLastTouchDownTime = ev.getDownTime();
if (preorderedList != null) {
// childIndex points into presorted list, find original index
for (int j = 0; j < childrenCount; j++) {
if (children[childIndex] == mChildren[j]) {
mLastTouchDownIndex = j;
break;
}
}
} else {
mLastTouchDownIndex = childIndex;
}
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
我只摘取了一部分,其他的逻辑呀,判断呀,你们自己研究吧,嘻嘻
但是提一句吧,感觉对你可能有用,因为我没细说这块,所以就不展开了。
TouchTarget : 记录每个子view的按下状态
我这里要说的是dispatchTransformedTouchEvent这个方法,它就是调起子元素的dispatchTouchEvent 然后完成事件分发的递归调用
然后走进方法
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
handled = child.dispatchTouchEvent(event);
}
可以看到如果没有子元素会调起父类的dispatchTouchEvent 也就是view 的dispatchTouchEvent方法,这个方法主要是调起onTouchEvent方法
2.View.onTouchEvent()
换一种方式,我把备注写在代码中了哈
public boolean onTouchEvent(MotionEvent event) {
final float x = event.getX();
final float y = event.getY();
final int viewFlags = mViewFlags;
final int action = event.getAction();
final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
if ((viewFlags & ENABLED_MASK) == DISABLED) {
if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
// A disabled view that is clickable still consumes the touch
// events, it just doesn't respond to them.
return clickable;
1. 返回clickable是为了如果不可点击,消耗此事件,不往下传递
}
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
2.TOOLTIP 给控件设置的长按提示信息
switch (action) {
case MotionEvent.ACTION_UP:
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
if ((viewFlags & TOOLTIP) == TOOLTIP) {
handleTooltipUp();
}
if (!clickable) {
removeTapCallback();
removeLongPressCallback();
mInContextButtonPress = false;
mHasPerformedLongPress = false;
mIgnoreNextUpEvent = false;
break;
}
boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
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);
}
if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
// 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)) {
performClickInternal();
}
}
}
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();
}
mIgnoreNextUpEvent = false;
break;
case MotionEvent.ACTION_DOWN:
if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN) {
mPrivateFlags3 |= PFLAG3_FINGER_DOWN;
}
mHasPerformedLongPress = false;
if (!clickable) {
checkForLongClick(
ViewConfiguration.getLongPressTimeout(),
x,
y,
TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);
break;
}
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) {
3. 判断是不是滚动事件
mPrivateFlags |= PFLAG_PREPRESSED;
if (mPendingCheckForTap == null) {
mPendingCheckForTap = new CheckForTap();
}
mPendingCheckForTap.x = event.getX();
mPendingCheckForTap.y = event.getY();
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
4.延时处理
} else {
// Not inside a scrolling container, so show the feedback right away
setPressed(true, x, y);
checkForLongClick(
ViewConfiguration.getLongPressTimeout(),
x,
y,
TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);
}
break;
case MotionEvent.ACTION_CANCEL:
if (clickable) {
setPressed(false);
}
removeTapCallback();
removeLongPressCallback();
mInContextButtonPress = false;
mHasPerformedLongPress = false;
mIgnoreNextUpEvent = false;
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
break;
case MotionEvent.ACTION_MOVE:
if (clickable) {
drawableHotspotChanged(x, y);
}
final int motionClassification = event.getClassification();
final boolean ambiguousGesture =
motionClassification == MotionEvent.CLASSIFICATION_AMBIGUOUS_GESTURE;
int touchSlop = mTouchSlop;
if (ambiguousGesture && hasPendingLongPressCallback()) {
final float ambiguousMultiplier =
ViewConfiguration.getAmbiguousGestureMultiplier();
if (!pointInView(x, y, touchSlop)) {
// The default action here is to cancel long press. But instead, we
// just extend the timeout here, in case the classification
// stays ambiguous.
removeLongPressCallback();
long delay = (long) (ViewConfiguration.getLongPressTimeout()
* ambiguousMultiplier);
// Subtract the time already spent
delay -= event.getEventTime() - event.getDownTime();
checkForLongClick(
delay,
x,
y,
TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);
}
touchSlop *= ambiguousMultiplier;
}
// Be lenient about moving outside of buttons
if (!pointInView(x, y, touchSlop)) {
// Outside button
// Remove any future long press/tap checks
removeTapCallback();
removeLongPressCallback();
if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
}
final boolean deepPress =
motionClassification == MotionEvent.CLASSIFICATION_DEEP_PRESS;
if (deepPress && hasPendingLongPressCallback()) {
// process the long click action immediately
removeLongPressCallback();
checkForLongClick(
0 /* send immediately */,
x,
y,
TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DEEP_PRESS);
}
break;
}
return true;
}
return false;
}
我也不知道该写点啥,哈哈
3、onInterceptTouchEvent
这个类就自己看吧,没啥,就是什么时候拦截,什么时候不拦截的判断
补充:
onTouchEvent中只有down事件的返回值有效,也就是宣布所有权的只有down事件
先这样吧,欢迎批评指正!!
喵印~~~