1、什么是事件传递?
Android事件传递是指用户操作屏幕产生的一系列动作事件(按下、滑动、抬起)从外层传递到的内层的过程。
2、外层到内层如何理解?
Activity —> Window ----> ViewGroup ---->View
3、必须了解的基础
3.1 首先要知道传递的对象是一个MotionEvent类的对象。这个类中定义了动作常量,比如“按下” public static final int ACTION_DOWN = 0等等。
3.2 第二要知道3个重要的方法 :
boolean dispatchTouchEvent(MotionEvent ev) 分发事件ev
boolean onInterceptTouchEvent(MotionEvent ev) 是否拦截事件ev
boolean onTouchEvent(MotionEvent ev) 是否消耗事件ev
3.3经常给view设置的有关事件监听的方法
view.setOnTouchListener(…) 触摸事件监听
view.setOnClickListener(…) 单击事件监听
4、事件传递的具体过程
4.1产生事件:
手指触碰屏幕首先产生一个MotionEvent Down事件,随着手指滑动产生多个MotionEvent Move事件,最后抬起手指产生一个MotionEvent Up事件
4.2 事件在Activity中:
Activity中有dispatchTouchEvent()、onTouchEvent()2个方法。
/**
* Called to process touch screen events. You can override this to
* intercept all touch screen events before they are dispatched to the
* window. Be sure to call this implementation for touch screen events
* that should be handled normally.
*
* @param ev The touch screen event.
*
* @return boolean Return true if this event was consumed.
*/
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
/**
* Called when a touch screen event was not handled by any of the views
* under it. This is most useful to process touch events that happen
* outside of your window bounds, where there is no view to receive it.
*
* @param event The touch screen event being processed.
*
* @return Return true if you have consumed the event, false if you haven't.
* The default implementation always returns false.
*/
public boolean onTouchEvent(MotionEvent event) {
if (mWindow.shouldCloseOnTouch(this, event)) {
finish();
return true;
}
return false;
}
dispatchTouchEvent中事件先去传给getWindow().superDispatchTouchEvent(ev)去做判断,如果返回为true,则直接结束。如果为false 触发Activity的onTouchEvent()方法。
也就是说在Activity中通过getWindow().superDispatchTouchEvent(ev)方法将事件传递给了Window。
4.3 事件在Window中:
getWindow().superDispatchTouchEvent(ev)调用Window内部的superDispatchTouchEvent()方法。
/**
* Used by custom windows, such as Dialog, to pass the touch screen event
* further down the view hierarchy. Application developers should
* not need to implement or call this.
*
*/
public abstract boolean superDispatchTouchEvent(MotionEvent event);
这是一个抽象方法。具体逻辑在Window的唯一继承类PhoneWidow中:
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
事件在window中没有做任务处理,直接通过mDecor.superDispatchTouchEvent(event)传递给了DecorView。DecorView是view的最顶层,被定义在PhoneWindow中的一个对象。它是一个继承自FrameLayout的ViewGroup。
// This is the top-level view of the window, containing the window decor.
private DecorView mDecor;
4.4 事件在ViewGroup中:
事件传递到DecorView的superDispatchTouchEvent()方法
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
调用了super.dispatchTouchEvent(event),这个super其实就是ViewGroup。
在ViewGroup中有dispatchTouchEvent()、onInterceptTouchEvent()、onTouchEvent()3个方法。
由于源码过长,其中还包含很多控制单元,为了分析过程直接贴出来不直观,不想篇幅过长,自己去查看印象更深刻!直接总结:
(1)事件MotionEvent传递给dispatchTouchEvent(ev)进行事件分发
(2)在分发方法内部调用了onInterceptTouchEvent(ev)判读是否对事件拦截(返回false代表不拦截,true表示拦截。默认为false)。
(3) onInterceptTouchEvent(ev)返回true, 说明要拦截事件,ev会交给此viewGroup处理。
(4)如果viewGroup设置了OnTouchListener,onTouch()会被调用,onTouch()返回false viewGroup的onTouchEvent会被调用。onTouch()返回true viewGroup的onTouchEvent会被屏蔽掉。
(5)onInterceptTouchEvent(ev)返回false,不拦截事件,将会把事件传递给 子View(view或viewGroup)的dispatchTouchEvent(ev)处理。如果子view是ViewGroup则继续从(1)开始循环。
4.4 事件在View中:
在View中有dispatchTouchEvent()、onTouchEvent()2个方法。没有onInterceptTouchEvent()
与ViewGroup过程相似,毕竟ViewGroup继承自View。ViewGroup也没有重写onTouchEvent,所以ViewGroup中onTouchEvent()方法就是View中的方法。
(1)事件MotionEvent传递给dispatchTouchEvent(ev)进行事件分发
(2)如果view设置了OnTouchListener,onTouch()会被调用,onTouch()返回false view的onTouchEvent会被调用。onTouch()返回true view的onTouchEvent会被屏蔽掉。
5、setOnTouchListener()\setOnClickListener()\onTouchEvent()调用关系:
下面是view的dispatchTouchEvent()源码
/**
* Pass the touch screen motion event down to the target view, or this
* view if it is the target.
*
* @param event The motion event to be dispatched.
* @return True if the event was handled by the view, false otherwise.
*/
public 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;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
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;
}
其中判断了mOnTouchListener != null && mOnTouchListener.onTouch(this, event)条件,之后根据返回结果判断是否去调用onTouchEvent(event)。验证了OnTouchListener 调用优先,并控制onTouchEvent()的调用。
在view的onTouchEvent()方法中
public boolean onTouchEvent(MotionEvent event) {
...代码省略...
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
case MotionEvent.ACTION_UP:
...代码省略...
if (!focusTaken) {
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
performClickInternal();
}
}
...代码省略...
}
...代码省略...
performClickInternal()方法会调performClick(),贴出performClick()源码
public boolean performClick() {
// We still need to call this method to handle the cases where performClick() was called
// externally, instead of through performClickInternal()
notifyAutofillManagerOnClick();
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;
}
这里触发了li.mOnClickListener.onClick(this)。说明点击事件onClick是在onTouchEvent中间接调用的。
6、事件交由上层处理
子视图不能处理事件时候,onTouchEvent 返回false,事件将交给父视图的onTouchEvent调用。如果还不能处理事件则依次往上传递。直到Activity。也就是在getWindow().superDispatchTouchEvent(ev)返回了false。将调用Activity的onTouchEvent()方法。