很多讲解Android事件分发的博客都提到了View
、ViewGroup
的事件分发,其实Android事件分发除了上述2个,还有一个就是Activity。
而Android事件分发的起点,就是从Activity开始的。在实际研发工作中,虽然我们用得更多的是ViewGroup
和View
的事件分发机制的知识,但从知识体系的完整性讲,Activity的事件分发机制地位同样重要。
说明: 本文在无特别说明下,源码对应的api版本均为: 28.0.0
老规矩,在正式学习Activity的事件分发机制之前,我们给出结论性的东西,先有一个整体的脉络。带着问题看源码,不容易陷入死胡同之中:
代码片1
/**
* 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) {
// ----->分析1
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
// ---->分析2
onUserInteraction();
}
// ------>分析3
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
// ---->分析4
return onTouchEvent(ev);
}
上述代码的逻辑比较简单: 首先判断MotionEvent
是不是ACTION_DOWN
,如果是的话,执行onUserInteraction()
方法,然后判断getWindow().superDispatchTouchEvent(ev)
是否为true
,如果为true
,就不会执行onTouchEvent()
方法,如果为false
则执行onTouchEvent()
方法。
一般事件列开始都是DOWN
事件 = 按下事件,故代码片1第10行代码一般为true
.进入到第17行代码中。
我们来看一下第17行代码(即onUserInteraction()
,分析2)的源码:
代码片2
/**
* Called whenever a key, touch, or trackball event is dispatched to the
* activity. Implement this method if you wish to know that the user has
* interacted with the device in some way while your activity is running.
* This callback and {@link #onUserLeaveHint} are intended to help
* activities manage status bar notifications intelligently; specifically,
* for helping activities determine the proper time to cancel a notfication.
*
* All calls to your activity's {@link #onUserLeaveHint} callback will
* be accompanied by calls to {@link #onUserInteraction}. This
* ensures that your activity will be told of relevant user activity such
* as pulling down the notification pane and touching an item there.
*
*
Note that this callback will be invoked for the touch down action
* that begins a touch gesture, but may not be invoked for the touch-moved
* and touch-up actions that follow.
*
* @see #onUserLeaveHint()
*/
public void onUserInteraction() {
}
这是一个空方法,什么都没有。还好有注释,不然都不知道这个方法是用来干嘛的了。当此activity
在栈顶时,触屏点击按home
,back
,menu
键等都会触发此方法。下拉statubar
、旋转屏幕、锁屏不会触发此方法。所以它会用在屏保应用上,因为当你触屏机器 就会立马触发一个事件,而这个事件又不太明确是什么,正好屏保满足此需求。
我们来看 getWindow().superDispatchTouchEvent(ev)
。
由代码片1,首先事件开始交给Activity所属的Window进行分发,如果返回true,整个事件循环结束,返回false意味着事件没人处理,所有View的onTouchEvent
都返回了false,那么Activity的onTouchEvent
就会被调用。
我们先看superDispatchTouchEvent()
代码片3
/**
* 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);
意思大概是说,这个方法会在Dialog
等界面中使用到,开发者不需要实现或者调用它。
我们注意到superDispatchTouchEvent()
方法是getWindow()调用的,getWindow()
方法返回的是一个Window
对象。源码中,对Window
类的描述是这样的:
代码片4
/**
* Abstract base class for a top-level window look and behavior policy. An
* instance of this class should be used as the top-level view added to the
* window manager. It provides standard UI policies such as a background, title
* area, default key processing, etc.
*
* The only existing implementation of this abstract class is
* android.view.PhoneWindow, which you should instantiate when needing a
* Window.
*/
上述代码(注释)的大概意思是: Window
类可以控制顶级View的外观和行为策略,它的唯一实现位于android.view.PhoneWindow
中,当你要实例化这个Window类的时候,你并不知道它的细节,因为这个类会被重构,只有一个工厂方法可以使用。
既然Window
的唯一实现类是PhoneWindow
,那我们直接看PhoneWindow
是如何处理点击事件的:
代码片5
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
到这里,逻辑就很清晰了,PhoneWindow
将事件直接传递到了mDecor
对象。那么mDecor
对象又是什么?
跳转到mDecor
的声明,代码如下:
代码片6
// This is the top-level view of the window, containing the window decor.
private DecorView mDecor;
再跳转到DecorView
类的定义处,发现这么一行代码:
代码片7
private final class DecorView extends FrameLayout implements RootViewSurfaceTaker {
......
}
现在我们清楚了,DecorView
继承自FrameLayout
,是我们编写所有的界面代码的父类。
然后我们看看mDecor.superDispatchTouchEvent()
这个方法干了什么,也就是在DecorView
类中,superDispatchTouchEvent()
方法的内容:
代码片8
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
上述代码调用的又是super.dispatchTouchEvent
,也就是DecorView
父类的dispatchTouchEvent
方法。
而我们知道, DecorView
是继承自FrameLayout
,FrameLayout
的父类就应该是ViewGroup
了,换句话说,最终 getWindow().superDispatchTouchEvent(ev)
其实就是 ViewGroup
的dispatchTouchEvent()
方法了。
总结一下: getWindow().superDispatchTouchEvent(ev)的值取决于,如果视图顶层的ViewGroup-DecorView类-的dispatchTouchEvnent()方法。
如果返回true,进入if体中, 整个dispatchTouchEvent返回true,即拦截事件,代码片1的第26行代码onTouchEvent()方法也不会执行了。
如果返回false,则执行代码片1的第26行代码onTouchEvent()方法。
关于ViewGroup的事件分发机制,请看这篇:Android事件分发机制完全解析(三) :ViewGroup的事件分发机制
Activity.onTouchEvent()
代码片9
/**
* 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;
}
代码逻辑很简单,第13行代码又是调用Window的方法。
我们在Window.java中搜索shouldCloseOnTouch()方法,代码如下
代码片10
/** @hide */
public boolean shouldCloseOnTouch(Context context, MotionEvent event) {
final boolean isOutside =
event.getAction() == MotionEvent.ACTION_DOWN && isOutOfBounds(context, event)
|| event.getAction() == MotionEvent.ACTION_OUTSIDE;
if (mCloseOnTouchOutside && peekDecorView() != null && isOutside) {
return true;
}
return false;
}
mCloseOnTouchOutside
是一个boolean
变量,它是由Window
的android:windowCloseOnTouchOutside
属性值决定。
isOutOfBounds(context, event)
是判断该event
的坐标是否在context
(对于本文来说就是当前的Activity)之外。是的话,返回true;否则,返回false。
peekDecorView()
则是返回PhoneWindow
的mDecor。
也就是说,如果设置了android:windowCloseOnTouchOutside
属性为true
,并且当前事件是ACTION_DOWN
,而且点击发生在Activity之外,同时Activity还包含视图的话,则返回true;表示该点击事件会导致Activity的结束。
比较典型的情况就是dialog形的Activity。
也就是说: Activity.onTouchEvent() 正常情况下都是返回false的。如果返回true,一方面拦截了事件,另外一方面finish当前Activity界面。