Android的MotionEvent事件分发机制
个人博客:http://zhangsunyucong.top
android事件的源头在哪里?
当用户触摸屏幕或者按键等时,形成事件,事件经过linux底层Event节点捕获之后,一直传到android应用层。中间传递的过程不是本文的重点,我也不是很清楚(哈哈哈)。本文的重点是事件在应用层的分发机制。
事件在View树中的分发过程
View树:
在Android中,事件的分发过程就是MotionEvent在view树分发的过程。默认是中从上而下,然后从下而上的传递的,直到有view、viewgroup或者Activity处理事件为止。
为什么要先从上而下?是为了在默认情况下,屏幕上层叠的所有控件都有机会处理事件。这个阶段我们称为事件下发阶段。
为什么要从下而上?是为了在从上而下分发时,事件没有控件处理时,再从下而上冒泡事件,是否有控件愿意处理事件。如果中间没有控件处理,事件就只能由Acitivity处理了。这个阶段我们称为事件的冒泡阶段。
准备
事件序列:从用户手指触摸屏幕开始,经过滑动到手指离开屏幕。这个操作产生了一个dowm事件,一系列move事件,最后一个up事件结束。我们把这一个操作产生的事件称为一个事件序列。
Acitivity中和事件传递有关的函数
事件分发:dispatchTouchEvent
事件处理:onTouchEvent
ViewGrop中和事件传递有关的函数
事件分发:dispatchTouchEvent
事件拦截:onInterceptTouchEvent
事件处理:onTouchEvent
View中和事件传递有关的函数
事件分发:dispatchTouchEvent
事件处理:onTouchEvent
从上面可以看出,ViewGrop中多了事件拦截onInterceptTouchEvent函数,是为了询问自己是否拦截事件(在事件分发中询问),如果没有拦截就传递事件给直接子view,如果拦截就将事件交给自己的事件处理函数处理。View中没有事件拦截函数,因为view是在view树中的叶节点,已经没有子view。
下面是先进行源码分析,然后再验证得出一些结论。代码迟点上传github。
用图表示布局的层级关系:
这里分析事件的分发过程,是从down事件的分发开始,以及分析它在两个阶段的传递过程:下发阶段和冒泡阶段。
事件下发阶段
(1)在Acitvity中的源码分析:
Activity#dispatchTouchEvent
public boolean dispatchTouchEvent(MotionEvent ev) { if (ev.getAction() == MotionEvent.ACTION_DOWN) { onUserInteraction(); } if (getWindow().superDispatchTouchEvent(ev)) { return true; } return onTouchEvent(ev);}
在第4行,Acivity将事件传递给了Window,Window是一个抽象类。在手机系统中它的实现是PhoneWindow.下面进入PhoneWindow中。
PhoneWindow#superDispatchTouchEvent
@Overridepublic boolean superDispatchTouchEvent(MotionEvent event) { return mDecor.superDispatchTouchEvent(event);}
从上面可以看出,事件已经从Acitivity到PhoneWindow,再传到了DecorView。DecorView是一个继承FrameLayout的ViewGroup,从而事件进入了View树的传递。
重写在Acitvity中的事件传递方法
重写Activity#dispatchTouchEvent:
1、返回false,事件不分发,所有事件在Acitivity的分发函数中就中断(真的不见了),连Acitivity的事件处理函数都到达不了。
2、返回true,所有事件在Acitivity的分发函数中就中断,和false一样
3、返回父函数方法,事件就传给直接子view分发
(2)在ViewGruop中的源码分析:
ViewGruop#dispatchTouchEvent
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. cancelAndClearTouchTargets(ev); 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;}
在5-11行,是每个新的事件系列开始前,会重置事件相关的状态。这里我们关注两个地方。第一个是第17行的disallowIntercept标志,第二个是第19行调用了事件拦截函数,询问是否拦截事件。
ViewGruop#onInterceptTouchEvent
public boolean onInterceptTouchEvent(MotionEvent ev) { if (ev.isFromSource(InputDevice.SOURCE_MOUSE) && ev.getAction() == MotionEvent.ACTION_DOWN && ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY) && isOnScrollbarThumb(ev.getX(), ev.getY())) { return true; } return false;}
onInterceptTouchEvent的代码很简单。
重写在ViewGroup中的事件传递方法
重写ViewGroup#dispatchTouchEvent:
1、返回false,不分发,down事件给父ViewGroup处理,以后的事件全部直接通过父ViewGroup分发函数给父ViewGroup的事件处理函数处理。
2、返回true,则所有的事件都从头来到这里就中断,不见了。
3、返回父函数方法,看下面拦截函数
重写ViewGroup#onInterceptTouchEvent(询问是否拦截):
1、返回true,就调用处理函数,在处理函数中是否消耗down事件
2、返回false,是否是最后一个view?否,down事件就分发给子View;是,就调用一次它的处理函数,进入冒泡阶段(就是一寸事件处理函数调用)
3、返回父函数的方法,和返回false一样
重写ViewGroup的onTouchEvent,当down事件来到中onTouchEvent时,
1、返回true,就消耗down事件,后面全部事件从头分发到处理函数(不用再询问是否拦截)。后面的事件根据是否消耗而是否消失(不消耗就消失),消失的所有事件由Acitivity处理(注意消失的事件也是从头传递到这里再传给Acitivity的)。
2、返回false,将down事件冒泡回去,看谁会处理。
3、返回父函数方法,是默认不消耗。
(3)在View中的源码分析:
View#dispatchTouchEvent
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; }}
这里关注的地方是,第9行和第13行。第9行是当前view如果设置了onTouch事件,并且它返回了true,那它就直接将result设置为true,事件就消耗了,不会再继续传递下去,只到达onTouch。第13行,是事件处理函数。可以看出onTouch是优先于onTouchEvent的。
View#onTouchEvent
....final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;... 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 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)) { performClick(); } } } ... } ... } return true;}
view根据是否可以点击等等一系列判断什么的。这里关注up事件中的第42-53行,有performClick。
View#performClick
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); notifyEnterOrExitForAutoFillIfNeeded(true); return result;}
如果view设置了mOnClickListener,即点击事件,会调用view的点击事件。如果在父view中拦截了up事件,使up事件到达不了这里,会使view的点击事件失效。
可以知道,onTouch是优先于onTouchEvent,onTouchEvent优先于onclick。
事件冒泡阶段
当down事件到达了最后一个子view,如果仍然没有view愿意处理它,就调用一次最后一个子view的事件处理函数,是否处理dowm事件,如果不处理,就一直冒泡回去,直到有view的onTouchEvent处理为止。如果都不处理,就只有Acitivity自己处理了。整个事件冒泡阶段就是一串onTouchEvent的回溯过程,自下而上。
更多相关文章
- Android下的图形处理
- 【Android】ViewPager实现图片左右滑动播放及添加点击事件
- 我的Android进阶之旅------>Android中解析XML 技术详解---->SAX
- Android中几种图像特效处理的集锦!!!
- Android(安卓)Touch 触摸事件
- 基于Eclipse的Android(安卓)JNI层测试应用开发过程记录
- Android核心分析(17) ------电话系统之rilD
- Android自动化测试工具——Monkey
- 箭头函数的基础使用