image.png
ACTION_CANCEL 举个栗子:当我们的外层View将事件传递给内层View去处理时,外层View的拦截方法一般会返回false,但是当某个条件触发后,外层View想自己处理接下来的事件,就拦截了事件分发,此时内层View就会收到ACTION_CANCEL的事件。
image.png
一个事件首先来到Activity ,再到window(PhoneWinodw),再到DocorView,通过setContentView设置的view是DocorView的子View。
PhoneWindow:
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
DocorView:
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
DocorView继承自FrameLayout, FrameLayout本身没有复写dispatchTouchEvent, 因此来到FrameLayout的父类ViewGroup的dispatchTouchEvent。
总结: Activity -> PhoneWindow -> DocorView -> ViewGroup
Activity处理事件
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
-
onUserInteraction() 主要用于判断用户是否与Activity有交互(另一次是调用Activity#onUserLeaveHint()之前会调用Activity#onUserInteraction(),比如按下home件会调用onUserLeaveHint())
-
接下来的getWindow().superDispatchTouchEvent(ev)调用路径为:
Activity -> PhoneWindow -> DocorView -> ViewGroup -> View
点击事件产生后,Activity会分发给window,继而分发给子View,如果被子View没有处理点击事件,会调用Activity的onTouchEvent()方法。
注意:Activity本身没有 onIntercetpTouchEvent方法,不难理解,Activity是分发事件的根,不需要拦截,只有viewGroup才需要拦截(view也没有拦截方法,也不需要拦截)。
ViewGroup处理事件
伪代码如下:
image.png
理解:
1.事件传递到ViewGroup时,dispatchTouchEvent方法会被调用。如果这个ViewGroup的onInterceptTouchEvent方法返回true,则表示它要拦截事件,事件就会交给当前ViewGroup的onTouchEvent方法处理。
2. 如果当前ViewGroup的onInterceptTouchEvent返回false,即不拦截事件,则会调用子元素的dispatchTouchEvent方法,这样就把事件传递给了子元素。(会遍历子view,根据触点判断要分发被的子view)
3. 如果子元素没有消耗事件,也就是子元素的dispatchTouchEvent方法返回false,那事件会由当前ViewGroup自己处理,当前ViewGroup的onTouchEvent会被调用。如果当前ViewGroup的dispatchTouchEvent方法也返回false,最后就会一层层往上,如果事件一直没有被消耗,那么最后Activity的onTouchEvent方法会被调用
注意:ViewGroup在ACTION_DOWN事件时开始拦截,
view处理事件
view没有onInterceptTouchEvent方法,因为已经是事件末端了,没有子 view了,不需要再拦截。
public boolean dispatchTouchEvent(MotionEvent event) {
。。。
if (onFilterTouchEventForSecurity(event)) {
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
if (!result && onTouchEvent(event)) {
result = true;
}
}
。。。
return result;
}
理解:
1.事件传递到view后,会首先判断是否设置了OnTouchListener,如果OnTouchListener的onTouch返回true,则不会调用onTouchEvent
2.事件传递到onTouchEvent后,只要view的Clickable和long_clickable有一个为true,那么就会消耗这个事件,onTouchEvent会返回true
这种情况下,当ACTION_UP事件后,会触发performClick方法,如果view设置了OnClickListener,那么performClick会调用onClick方法。
总结下来,优先级:OnTouchListener > onTouchEvent > OnClickListener
image.png
image.png
ACTION_MOVE、ACTION_UP:
从ViewGroup的分发代码看:
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
...
if (actionMasked == MotionEvent.ACTION_DOWN) {
cancelAndClearTouchTargets(ev);
//1. 会使disallowIntercept失效 2. 将mFirstTouchTarget置为null
resetTouchState();
}
// Check for interception.
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
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 (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary view.
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
}
如果子view设置disallowIntercept为true, 则不允许ViewGroup拦截除ACTION_DOWN之外的其他动作(ACTION_DOWN 会重置FLAG_DISALLOW_INTERCEPT标记)。
此时,Action为 ACTION_DOWN, 如果没有拦截,会遍历执行childView的dispatchTouchEvent,如果有View返回true, 则会将此View 赋值给mFirstTouchTarget,也就是确定了后续action的view对象。
如果 ACTION_DOWN被ViewGroup消费了,mFirstTouchTarget == null,后续的ACTION_MOVE等 都会走到 intercepted = true; 也就不会分发给子View。
如果ViewGroup拦截后续事件,则第一次拦截会将事件转为ACTION_CANCEL传递给目标消费子View(终止子View接收后续事件),接下来的后续事件自己消费。
其实ViewGroup的dispatchTouchEvent理解中的结论三就是这种情景,都是父视图的onInterceptTouchEvent先返回false,子控件消费事件(dispatchTouchEvent返回true),然后在某种情况下onInterceptTouchEvent返回true,子控件就会收到ACTION_CANCEL的Event。
比如:上层 View 是一个 RecyclerView,它收到了一个 ACTION_DOWN 事件,由于这个可能是点击事件,所以它先传递给对应 ItemView,询问 ItemView 是否需要这个事件,然而接下来又传递过来了一个 ACTION_MOVE 事件,且移动的方向和 RecyclerView 的可滑动方向一致,所以 RecyclerView 判断这个事件是滚动事件,于是要收回事件处理权,这时候对应的 ItemView 会收到一个 ACTION_CANCEL ,并且不会再收到后续事件。
————————————————
版权声明:本文为CSDN博主「我的心里只有你」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/y444400/article/details/53435696
refer:
https://www.jianshu.com/p/b173ada7b496
https://juejin.im/post/5dcbc6ad5188250cfe60cf48
https://dreamerhome.github.io/2016/07/25/vieweventdispatch/
https://zhuanlan.zhihu.com/p/55297318
https://www.jianshu.com/p/e99b5e8bd67b










网友评论