安卓之事件分发机制
简介
- 事件分发的"事件"是指什么?
答:点击事件(Touch事件)。 - 当用户触摸屏幕(VIew或ViewGroup)时,将产生点击事件,即Touch事件。Touch事件的细节(如:触摸位置、事件等)会被封装成MotionEvent对象。
- 常见的事件:
- MotionEvent.ACTION_DOWN:按下。
- MotionEvent.ACTION_UP:抬起。
- MotionEvent.ACTION_MOVE:滑动。
- MotionEvent.ACTION_CANCEL:结束事件(非正常情况)。
- 事件列:指从手指接触屏幕至手指离开屏幕这个过程产生的一系列事件。
- 一般情况下,事件列都是以DOWN事件开始、UP事件结束,中间有无数的MOVE事件。
本质
- 事件分发的本质是将点击事件(MotionEvent)传递到某个具体的View进行处理的整个过程。即:事件传递的过程 = 分发过程。
事件在哪些对象之间进行传递?
- 答:Activity、ViewGroup、View。
事件分发的顺序是?
- 答:Activity -> ViewGroup -> View。
事件分发过程由哪些方法协作完成?
- 答:dispatchTouchEvent() 、onInterceptTouchEvent()和onTouchEvent()。
Activity的事件分发机制
当用户触摸屏幕时,底层的 InputManager 会将触摸事件封装为 MotionEvent 对象,然后将该对象传递给当前 Activity 的 PhoneWindow 对象。PhoneWindow 对象会将该 MotionEvent 传递给 DecorView 对象,DecorView 再传递给Atcivity的 dispatchTouchEvent() 方法进行事件分发或处理。Atcivity的 dispatchTouchEvent() 方法如下:
java
public boolean dispatchTouchEvent(MotionEvent ev) {
...
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
View的事件分发机制
-
先看源码:
*javapublic boolean dispatchTouchEvent(MotionEvent event) { //...省略代码 //以下为核心代码: //这里就是View在收到事件后的具体处理逻辑了,也就是ViewGroup调用了View的dispatchTouchEvent()方法把事件传递给了View //先判断有没有给View设置OnTouchListener,如果有就执行OnTouchListener的onTouch()方法 //同时该方法有返回值表示事件有没有被消费,如果被消费了,result设置为true //接着是判断如果没有被消费,则执行onTouchEvent()方法,这个方法中会沿着MotionEvent.ACTION_UP->performClickInternal()->performClick()->OnClickListener的onClick(),最后走到了点击监听的onClick()中。 //同时,onClick()也是用返回值表示事件有没有被消费。 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; } } //...省略代码 }
-
简单总结:
- View中按onTouch()->onTouckEvent()->onClick()的顺序执行。
- 同理,你的自定义View如果需要处理事件分发,那就在dispatchTouchEvent()方法中。
-
以上就是View中最核心的事件分发处理流程了,同时延伸出几个可以提问的问题:
- ①问:onTouch()和onClick()谁先执行?
答:源码的顺序就是先走onTouch(),再走onClick()。 - ②问:onTouch()方法返回值的作用?
答:不仅仅是onTouch()方法,几乎所有的方法返回true表示事件被消费,返回false表示事件未被消费,可以继续分发。 - ③问:onTouchEvent()一定执行吗?
答:不一定,如果设置了OnTouchListener,则先看onTouch()拦不拦截,不拦截才会走onTouchEvent()方法。同时如果是自定义View,还要看有没有重写dispatchTouchEvent()方法。 - ④问:onTouch()和onTouchEvent一定会执行吗?
答:不一定,首先事件得先传递到dispatchTouchEvent()中,其次看各种条件是否能通过。 - ⑤问:如何分析事件分发冲突?
答:就是查看对应的控件的dispatchTouchEvent()是怎么么处理的,看事件有没有传递进去,传递进去后又是在哪消费的。 - ⑥问:
- 答:
- ①问:onTouch()和onClick()谁先执行?
Activity的事件分发机制
-
先看源码:
*java//事件是怎么传递到Activity的dispatchTouchEvent()方法中的? // //Activity中进行分发: // 这里调用沿着getWindow().superDispatchTouchEvent(ev) // ->Window的唯一实现类PhoneWindow的mDecor.superDispatchTouchEvent(event) // ->DecorView的super.dispatchTouchEvent(event), // 最后传递到ViewGroup中,ViewGroup又进行事件分发。 // //如果事件在Activity->ViewGroup->View整个传递过程中没有被处理, //最后又交回Activity的onTouchEvent()进行处理, //再如果Activity也不处理,那只能交给系统处理了。 public boolean dispatchTouchEvent(MotionEvent ev) { if (ev.getAction() == MotionEvent.ACTION_DOWN) { onUserInteraction(); } if (getWindow().superDispatchTouchEvent(ev)) { return true; } return onTouchEvent(ev); }
ViewGroup的事件分发机制
-
先看源码:
*java//第一块:ViewGroup每次事件分发时,都需调用onInterceptTouchEvent()询问是否拦截事件。 //这里两个关键的地方:disallowIntercept和onInterceptTouchEvent()。 // disallowIntercept:是否禁用事件拦截的功能(默认是false),可通过调用requestDisallowInterceptTouchEvent()修改。 // onInterceptTouchEvent(): // a.若在onInterceptTouchEvent()中返回false,即当前ViewGroup不拦截事件。 // b. 若在onInterceptTouchEvent()中返回true,即拦截事件。 // 这个方法中一般就是解决事件分发冲突的,本质就是事件拦不拦截,交由谁处理。 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); } else { intercepted = false; } } else { intercepted = true; } //第二块:分发事件,看哪个子View处理事件 //通过for循环,遍历当前ViewGroup下的所有子View,根据点击的位置判断当前遍历的View是不是正在点击的View,从而找到当前被点击的View。 //dispatchTransformedTouchEvent()方法:内部根据传入的childView是否为空来决定调用当前View还是子View的dispatchTouchEvent()方法。 if (!canceled && !intercepted) { //..省略代码 for (int i = childrenCount - 1; i >= 0; i--) { if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) { 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; } ev.setTargetAccessibilityFocus(false); } } //第三块: //执行事件。 if (mFirstTouchTarget == null) { // No touch targets so treat this as an ordinary view. handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS); } else { //...省略代码 }