很多人都是学了忘,忘了学,感觉永远也记不住。View 事件分发到底应该怎么学?
其实很简单:
1、敲代码
2、学习原理
3、画流程图
View 事件分发 其实就4️部分组成
1、 dispatchTouchEvent(MotionEvent ev)
2、 onInterceptTouchEvent(MotionEvent ev)
3、 onTouchEvent(MotionEvent ev)
4、 MotionEvent
那么我们就详细的了解一下这四个的具体含义。
dispatchTouchEvent(MotionEvent ev)
从名字就可以看出这个方法主要用来事件的分发,每个 View 都有这个方法,如果消息能够传递到这个 View,那么这个方法一定能执行。返回 true 代表消耗这个事件,false 表示不消耗。这个结果受两个因素的影响。
1、当前 View 的 onTouchEvent() 的返回值。
2、下级 View dispatchTouchEvent() 方法的返回值
onInterceptTouchEvent(MotionEvent ev)
这个方法是在dispatchTouchEvent() 中调用,,事件拦截,这个只有在 ViewGroup 才有方法,View 是没有的。如果当前 View 拦截了这个事件,那么在一个事件序列中这个方法不会被再次调用了。直到这个事件序列结束。返回的结果表示是否消耗了这个事件。
onTouchEvent(MotionEvent ev)
这个方法是在dispatchTouchEvent() 中调用,用来处理点击事件,返回结果表示是否消耗了这个事件。如果不消耗,返回 false 那么在同一系列事件中,将不会再给这个事件派发,也就是再次收到事件。
在艺术探索一书中有这么一段伪代码
public boolean dispatchTouchEvent(MotionEvent ev){
boolean consume = false;
if(onInterceptTouchEvent(ev)){
consume = onTouchEvent(ev);
}else{
consume = child.dispatchTouchEvent(ev);
}
return consume;
}
我们可以通过记忆这一段简单的伪代码,理解整个 View 的事件分发。
1、一个事件序列
2、事件分发顺序
通过伪代码和图示我们大概了解了 View 的事件分发,但是更具体的还需要源码去分析。
我们便从 Activity 到 View 一起来走一遍,看看具体代码是怎么实现的,里面有什么细节给我们解惑的。
1、Activity
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
我们结合上图和源码,可以看到很简单的几段代码,事件分发调用的是 Windowd 的superDispatchTouchEvent 分发。接着往下看。
我们知道 Window 是个抽象类,所以用实现类 PhoneWindow。
mWindow = new PhoneWindow(this, window, activityConfigCallback);
PhoneWindow 又托管给 DecorView ,就是我们说的 RootView 或者 RootViewGroup。
mDecor = (DecorView) preservedWindow.getDecorView();
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
其实就是 ViewGroup 的dispatchTouchEvent 方法。
public boolean dispatchTouchEvent(MotionEvent ev) {
······
//从这里开始 默认 handled 为 false 表示不消耗
boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
// Handle an initial down.
//当事件 是 按下的时候 也是这一事件系列的开始
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Throw away all previous state when starting a new touch gesture.
// The framework may have dropped the up or cancel event for the previous gesture
// due to an app switch, ANR, or some other state change.
// mFirstTouchTarget = null;重置mFirstTouchTarget 和 FLAG_DISALLOW_INTERCEPT
cancelAndClearTouchTargets(ev);
resetTouchState();
}
// 表示是否拦截
final boolean intercepted;
//当事件类型为 ACTION_DOWN 或者 mFirstTouchTarget 不为空的时候
//什么是 mFirstTouchTarget ?整体分析可知 当这个事件被子 view 消耗的时候,
//mFirstTouchTarget 会指向这个 view,也就是第一个消耗这个事件的 view。
//当 ViewGroup 不拦截的时候且子 view消耗了这个事件 那么mFirstTouchTarget !=null。
//那么 当事件类型为 MOVE 或者 UP 的时候,如果 mFirstTouchTarget !=null 那就说明
//有 view 消耗这个事件了,该系列的事件都默认交给它处理
//也就是事件的开始 和 事件有消耗后 都会走这个方法。当然也不是绝对的
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
//FLAG_DISALLOW_INTERCEPT这个参数是子 view 通过requestDisallowInterceptTouchEvent 方法,改变这个值 ,来控制 父 view 是否拦截
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
//当需要拦截的时候 那就走自己的onInterceptTouchEvent 方法具体自己实现 ,不需要那就intercepted 为 false
//需要注意的是 对于 DOWN 子 view 通过 requestDisallowInterceptTouchEvent 方法是控制不了父 view 的拦截方法的,因为当有 DOWN 事件的时候 就会触发 之前的重置方法。
//所以只能针对 MOVE UP 等,可以让 父 view 不在拦截事件,可以使父 view 开始拦截事件
//正是因为 FLAG_DISALLOW_INTERCEPT ,如果我们父 view 开始拦截,并消耗了这个事件,那么 mFirstTouchTarget!=null 就不会成立,因为ViewGroup 的子 view 成功消耗这个事件后 才会mFirstTouchTarget!=null。所以以后的事件也就不会再走 onInterceptTouchEvent,直接返回 intercepted = true 。
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
} else {
// There are no touch targets and this action is not an initial down
// so this view group continues to intercept touches.
intercepted = true;
}
// If intercepted, start normal event dispatch. Also if there is already
// a view that is handling the gesture, do normal event dispatch.
if (intercepted || mFirstTouchTarget != null) {
ev.setTargetAccessibilityFocus(false);
}
// Check for cancelation.
final boolean canceled = resetCancelNextUpFlag(this)
|| actionMasked == MotionEvent.ACTION_CANCEL;
// Update list of touch targets for pointer down, if needed.
final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
TouchTarget newTouchTarget = null;
boolean alreadyDispatchedToNewTouchTarget = false;
//当ViewGroup不拦截时,这里对 ViewGroup 的子 view 进行处理
if (!canceled && !intercepted) {
// If the event is targeting accessiiblity focus we give it to the
// view that has accessibility focus and if it does not handle it
// we clear the flag and dispatch the event to all children as usual.
// We are looking up the accessibility focused host to avoid keeping
// state since these events are very rare.
View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
? findChildWithAccessibilityFocus() : null;
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
final int actionIndex = ev.getActionIndex(); // always 0 for down
final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
: TouchTarget.ALL_POINTER_IDS;
// Clean up earlier touch targets for this pointer id in case they
// have become out of sync.
removePointersFromTouchTargets(idBitsToAssign);
final int childrenCount = mChildrenCount;
if (newTouchTarget == null && childrenCount != 0) {
final float x = ev.getX(actionIndex);
final float y = ev.getY(actionIndex);
// Find a child that can receive the event.
// Scan children from front to back.
final ArrayList<View> preorderedList = buildTouchDispatchChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
//遍历子 view
for (int i = childrenCount - 1; i >= 0; i--) {
//这一堆的代码有点复杂 不过就一个目的 当前子 view 是都能接受点击事件
//如何判断呢? 1、是否在播放动画 2、点击的坐标是否在该子 view 的区域
final int childIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);
// If there is a view that has accessibility focus we want it
// to get the event first and if not handled we will perform a
// normal dispatch. We may do a double iteration but this is
// safer given the timeframe.
if (childWithAccessibilityFocus != null) {
if (childWithAccessibilityFocus != child) {
continue;
}
childWithAccessibilityFocus = null;
i = childrenCount - 1;
}
//这就是判断是否可以点击的方法之一 view 要显示,是否在播放动画
/**
* Returns true if a child view can receive pointer events.
* @hide
* private static boolean canViewReceivePointerEvents(@NonNull View child) {
* return (child.mViewFlags & VISIBILITY_MASK) == VISIBLE
* || child.getAnimation() != null;
* }
*/
//判断二 是否在子 view 的范围
/**
* protected boolean isTransformedTouchPointInView(float x, float y, View child,
PointF outLocalPoint) {
final float[] point = getTempPoint();
point[0] = x;
point[1] = y;
transformPointToViewLocal(point, child);
final boolean isInView = child.pointInView(point[0], point[1]);
if (isInView && outLocalPoint != null) {
outLocalPoint.set(point[0], point[1]);
}
return isInView;
}
*
*/
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
//mFirstTouchTarget 是一个链表形式 遍历这个链表 如果有这个 view 那么就直接赋值 newTouchTarget
//注意 mFirstTouchTarget 是在View 的子 view中赋值的
// 说明点击确实在子 view 的区域 直接返回不用再继续遍历了
newTouchTarget = getTouchTarget(child);
if (newTouchTarget != null) {
// Child is already receiving touch within its bounds.
// Give it the new pointer in addition to the ones it is handling.
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}
resetCancelNextUpFlag(child);
//实际是 调用 子 view 的 dispatchTouchEvent 方法。依次递归下去
//如果 子 view dispatchTouchEvent 返回true 那么 newTouchTarget = addTouchTarget(child, idBitsToAssign);
// newTouchTarget 被赋值并跳出循环 其实是 mFirstTouchTarget链表添加数据
alreadyDispatchedToNewTouchTarget = true;
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;
}
// The accessibility focus didn't handle the event, so clear
// the flag and do a normal dispatch to all children.
ev.setTargetAccessibilityFocus(false);
}
if (preorderedList != null) preorderedList.clear();
}
//当 newTouchTarget 还是null 那就 链表 最后一个 赋值给newTouchTarget
if (newTouchTarget == null && mFirstTouchTarget != null) {
// Did not find a child to receive the event.
// Assign the pointer to the least recently added target.
newTouchTarget = mFirstTouchTarget;
while (newTouchTarget.next != null) {
newTouchTarget = newTouchTarget.next;
}
newTouchTarget.pointerIdBits |= idBitsToAssign;
}
}
}
// Dispatch to touch targets.
if (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary view.
//当 mFirstTouchTarget 为空 其实也就是子 view 为空,调用 view 的 dispatchTouchEvent 方法
//就是 viewGroup 如果需要消耗事件 其实调用的是 view 的方法
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
// Dispatch to touch targets, excluding the new touch target if we already
// dispatched to it. Cancel touch targets if necessary.
TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
while (target != null) {
final TouchTarget next = target.next;
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
} else {
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
if (cancelChild) {
if (predecessor == null) {
mFirstTouchTarget = next;
} else {
predecessor.next = next;
}
target.recycle();
target = next;
continue;
}
}
predecessor = target;
target = next;
}
}
// Update list of touch targets for pointer up or cancel, if needed.
//更新重置 状态
if (canceled
|| actionMasked == MotionEvent.ACTION_UP
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
resetTouchState();
} else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
final int actionIndex = ev.getActionIndex();
final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
removePointersFromTouchTargets(idBitsToRemove);
}
}
if (!handled && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
}
//最后返回 消耗的情况
return handled;
}
我们来看下 ViewGroup 的分发。
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
// Canceling motions is a special case. We don't need to perform any transformations
// or filtering. The important part is the action, not the contents.
final int oldAction = event.getAction();
if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
event.setAction(MotionEvent.ACTION_CANCEL);
//当没有子 view 那么它就相当于 view 调用 view 的方法。
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
//如果有 那么就 递归这个方法了
handled = child.dispatchTouchEvent(event);
}
event.setAction(oldAction);
return handled;
}
// Calculate the number of pointers to deliver.
final int oldPointerIdBits = event.getPointerIdBits();
final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;
// If for some reason we ended up in an inconsistent state where it looks like we
// might produce a motion event with no pointers in it, then drop the event.
if (newPointerIdBits == 0) {
return false;
}
// If the number of pointers is the same and we don't need to perform any fancy
// irreversible transformations, then we can reuse the motion event for this
// dispatch as long as we are careful to revert any changes we make.
// Otherwise we need to make a copy.
final MotionEvent transformedEvent;
if (newPointerIdBits == oldPointerIdBits) {
if (child == null || child.hasIdentityMatrix()) {
//当没有子 view 那么它就相当于 view 调用 view 的方法。
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
event.offsetLocation(offsetX, offsetY);
handled = child.dispatchTouchEvent(event);
event.offsetLocation(-offsetX, -offsetY);
}
return handled;
}
transformedEvent = MotionEvent.obtain(event);
} else {
transformedEvent = event.split(newPointerIdBits);
}
// Perform any necessary transformations and dispatch.
if (child == null) {
//当没有子 view 那么它就相当于 view 调用 view 的方法。
handled = super.dispatchTouchEvent(transformedEvent);
} else {
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
transformedEvent.offsetLocation(offsetX, offsetY);
if (! child.hasIdentityMatrix()) {
transformedEvent.transform(child.getInverseMatrix());
}
handled = child.dispatchTouchEvent(transformedEvent);
}
// Done.
transformedEvent.recycle();
return handled;
}
其实我们发现这一切都和 mFirstTouchTarget 有关系,当 viewGroup 需要自己消耗事件的时候,那么 mFirstTouchTarget 肯定是 null,也就是子 view 不消耗,或者自己拦截,都会造成 mFirstTouchTarget ==null,那么当调用 dispatchTransformedTouchEvent 方法是,就会触发 view 的 onTouchEvent 方法,viewGroup 自己去处理事件。
首先我们看看 View 的 dispatchTouchEvent() 方法(只保留了我们需要的源码)。
boolean dispatchTouchEvent(MotionEvent event) {
// If the event should be handled by accessibility focus first.
if (event.isTargetAccessibilityFocus()) {
// We don't have focus or no virtual descendant has it, do not handle the event.
if (!isAccessibilityFocusedViewOrHost()) {
return false;
}
// We have focus and got the event, then use normal event dispatch.
event.setTargetAccessibilityFocus(false);
}
//这里是开始
boolean result = false;
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(event, 0);
}
final int actionMasked = event.getActionMasked();
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Defensive cleanup for new gesture
stopNestedScroll();
}
if (onFilterTouchEventForSecurity(event)) {
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;
//判断是否有监听事件 并且 mOnTouchListener不为空
//view 的 enabled 为true 并且onTouch方法也返回true
//那么直接返回 true
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
//当 onTouch 被调动 且返回true 那么onTouchEvent不会调用了
if (!result && onTouchEvent(event)) {
result = true;
}
}
if (!result && mInputEventConsistencyVerifier != null) {
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.
if (actionMasked == MotionEvent.ACTION_UP ||
actionMasked == MotionEvent.ACTION_CANCEL ||
(actionMasked == MotionEvent.ACTION_DOWN && !result)) {
stopNestedScroll();
}
return result;
}
我们会发现 View 可以调用 onTouch() 且返回 true, onTouchEvent就不会再调用了,这就是一个优先级的问题了。
最后是 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;
//就算 不可用的情况下 也可能会消耗点击事件 只要 clickable 为true
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;
}
//如果 view 有代理会做这个方法 具体不说了
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
//只要控件的clickable 或者 long_clickable 或者 CONTEXT_CLICKABLE 有一个为 true
if (clickable || (viewFlags & TOOLTIP) == 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
//这是 up 所以 长按事件回调移除
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.
// 我们设置点击事件 是在这儿 开始的~~看好了 我们发现是通过 post 把 run 里的调用 performClick() 方法。用 Runnable ,通过 post 形式执行 performClick,而不是直接调用 performClick , 因为这可以让这个 view 更新的视觉状态在这个方法执行之前。
//这个比如当这个 view 绘制 显示 完成之后 才可以 点击 之类的 等等
if (mPerformClick == null) {
mPerformClick = new 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();
}
mIgnoreNextUpEvent = false;
break;
case MotionEvent.ACTION_DOWN:
if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN) {
mPrivateFlags3 |= PFLAG3_FINGER_DOWN;
}
mHasPerformedLongPress = false;
if (!clickable) {
checkForLongClick(0, x, y);
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) {
mPrivateFlags |= PFLAG_PREPRESSED;
if (mPendingCheckForTap == null) {
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;
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);
}
// Be lenient about moving outside of buttons
if (!pointInView(x, y, mTouchSlop)) {
// Outside button
// Remove any future long press/tap checks
removeTapCallback();
removeLongPressCallback();
if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
}
break;
}
//当 clickable 为 true 那么一定返回 true 不管执行了 点击事件啥 或者 其他 这个事件在这儿一定会消耗
//就是返回 true 所以当设置 不可点击的时候且clickable 为false 才会不消耗这个事件
return true;
}
return false;
}
超级会员:mOnTouchListener
会员:onTouchEvent
普通用户:setOnClickListener
最后根据上面的源码分析后,我们整理一些结论,结合结论和源码分析,我们更好的理解 View的事件分发。(copy from 《艺术探索》 + 自己的理解)
1、同一个事件序列是从手指触摸屏幕的那一刻开始,直到手指离开屏幕的那一刻结束,在这个过程产生的一系列事件,是从 down 开始 经过 n 个 move 最后 up 结束。
2、正常情况下,一个事件只能被一个 View 拦截且消耗,原因参考 3。一旦一个 View 拦截了某事件,那么这个事件序列内的所有事物都会直接交给它处理,因此同一个事件序列中的事件不能由两个 view 同时处理,注意是同时,但是通过特殊手段可是,比如将一个View 的事件强行传递给其他view,这是无赖操作。
3、某个 View 一旦决定拦截,那么这个事件序列只能由他来出来,如果事件能传递给他,而且他的 onInterceptTouchEvent 不会再继续调用,因为 View 拦截那么mFirstTouchTarget 就不会在 子 View 中赋值,mFirstTouchTarget ==null 直接返回 true ,最后走 View 的 ouTouchEvent 方法。
4、某个 View 一旦开始处理事件,如果它不消耗 ACTION_DOWN 事件( onTouchEvent 返回 false),那么同一序列的而其他事件都不会交给他处理,而是将事件重新交给他的父元素处理,即 父 View 的 onTouchEvent 会调用 。源码上其实就是 比如子 View onTouchEvent 返回 false,因为 dispatchTouchEvent 是 递归的,所以返回 子 view 的dispatchTouchEvent 返回 false,最后 mFirstTouchTarget 还是null ,所以 父 view(ViewGroup) 调用 View 的 dispatchTouchEvent 进行处理,最后父 View调用onTouchEvent 方法,来处理这个事件。
5、如果 View 不消耗 ACTION_DOWN 以外的其他事件,那么这个点击事件就会消失,此时的父元素的 onTouchEvent 不会调用,而且当前 View 可以持续收到后续的事件,最终这些消失的事件会交给 Activity 处理。
6、ViewGroup 默认不拦截任何事件,即 onInterceptTouchEvent 返回 false。
7、View 没有 onInterceptTouchEvent。事件传递给它,ouTouchEvenr 就会调用。
8、View 的 ouTouchEvent 会默认消耗事件 返回true。除非他是不可点击 clickable 和longclickable 同时是 false。longclickable默认为false,button 默认 clickable 是true,textview 默认 false。
9、View 的enable 属性不影响 ouTouchEvent 的返回值,只要longclickable 或 clickable 有一个是 true ,那么ouTouchEvent 就返回 true。
10、onClick 会发生的前提是 view 可点击。并过去收到了down 和up。
11、事件传递是从外向内的,事件总是先传递给父view,然后再有父view分发给子view,通过 requestDisallowInterceptTouchEvent 方法,子类可以干预父 View 的事件分发过程。ACTION_DOWN 除外。
12、mOnTouchListener > onTouchEvent > setOnClickListener
参考 《Android开发艺术探索》