零、前言
发布的文章已经详细介绍 View事件分发 和 ViewGroup事件分发 ,了解点击事件如何在ViewGroup和View内部流动。如果把两者联系起来,容易知道ViewGroup把事件分发给View,当View不拦截事件时又把事件返回给ViewGroup。
本文研究Activity事件分发,探究事件如何从Activity分发到ViewGroup,ViewGroup不拦截事件Activity又如何处理。文章最终解释Activity、ViewGroup、Group三者事件分发行为如何形成闭环。
上图只是事件分发最基本的示意,实际分发细节要复杂得多。Activity事件分发源码基于Android 27。另外两篇文章已经说明ViewGroup和View,Activity再遇到相关知识不深究,请自行翻阅前文。
一、Activity事件分发
1.1 dispatchTouchEvent()
点击事件最先被分发到Activity上,首个处理事件方法是dispatchTouchEvent(),重写方法可在所有事件分发到window
前就进行拦截。方法参数MotionEvent ev
是点击的事件类。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public boolean dispatchTouchEvent(MotionEvent ev) {
// ACTION_DOWN事件触发onUserInteraction()
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
// 事件交给window处理
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
// window没有消费该事件,交给Activity.onTouchEvent()处理
return onTouchEvent(ev);
}
1.2 onUserInteraction()
无论是按钮、触摸还是轨迹球事件都会分发给Activity。可以重写此方法,在activity运行过程中捕获用户与设备的交互事件。方法相当于一个回调,和onUserLeaveHint()
一样,是为了帮助activity智能地管理状态栏的通知,尤其是在合适时间点取消一个与之相关的通知。
1
2
public void onUserInteraction() {
}
所有对onUserLeaveHint()
的调用会同时伴随着对onUserInteraction()
的调用,确保activity在一些关于用户操作,如向下拉并点击了通知时得到告知。方法只在ACTION_DOWN
才会触发。
1.3 onUserLeaveHint()
和onUserInteraction()
有关的方法。作为activity生命周期的一部分,用户把activity退到后台的时候调用方法。例如用户点击Home键onUserLeaveHint()
就会被调用。
1
2
protected void onUserLeaveHint() {
}
但显示来电导致activity被中断并退到后台时,onUserLeaveHint()
不会调用。在onPause()
生命周期调用前先触发此方法。
1.4 performUserLeaving()
onUserInteraction()
和onUserLeaveHint()
都在用户离开时被回调,通过此方法串联起来:
1
2
3
4
final void performUserLeaving() {
onUserInteraction();
onUserLeaveHint();
}
1.5 小结
二、window.superDispatchTouchEvent()
2.1 activity.getWindow()
getWindow()返回Window抽象类,superDispatchTouchEvent()
是window的抽象方法。由自定义的windows调用,透过视图层级传递屏幕点击事件,例如Dialog。
1
public abstract boolean superDispatchTouchEvent(MotionEvent event);
2.2 PhoneWindow.superDispatchTouchEvent()
window的实现类是PhoneWindow,实际调用PhoneWindow.superDispatchTouchEvent()
,进而调用mDecor.superDispatchTouchEvent(event)
。
DecorView是PhoneWindow的成员变量。有很多文章提到DecorView是PhoneWindow内部类。但从Android27
看来,DecorView是独立的类而不是内部类。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 这是windows的的顶级视图,包含了window decor
private DecorView mDecor;
// activity中主window的构造方法
public PhoneWindow(Context context, Window preservedWindow,
ActivityConfigCallback activityConfigCallback) {
this(context);
// 只有主windows可使用decor context,其他windows则由传递给它们的context决定
mUseDecorContext = true;
if (preservedWindow != null) {
mDecor = (DecorView) preservedWindow.getDecorView();
mElevation = preservedWindow.getElevation();
mLoadElevation = false;
mForceDecorInstall = true;
// If we're preserving window, carry over the app token from the preserved
// window, as we'll be skipping the addView in handleResumeActivity(), and
// the token will not be updated as for a new window.
getAttributes().token = preservedWindow.getAttributes().token;
}
// 即使设备不支持画中画模式,用户也可通过开发者选项强行使用
boolean forceResizable = Settings.Global.getInt(context.getContentResolver(),
DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES, 0) != 0;
mSupportsPictureInPicture = forceResizable || context.getPackageManager().hasSystemFeature(
PackageManager.FEATURE_PICTURE_IN_PICTURE);
mActivityConfigCallback = activityConfigCallback;
}
调用DecorView的superDispatchTouchEvent()
1
2
3
4
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
在Activity类中,在onAttach()方法创建PhoneWindow实例
1
2
3
4
5
6
7
8
9
10
final void attach(Context context, ....){
....
mWindow = new PhoneWindow(this, window, activityConfigCallback);
mWindow.setWindowControllerCallback(this);
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
// 初始化了LayoutInflater配置
mWindow.getLayoutInflater().setPrivateFactory(this);
....
}
2.3 DecorView.superDispatchTouchEvent()
调用super.dispatchTouchEvent()
1
2
3
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
2.4 FrameLayout.dispatchTouchEvent()
由DecorView父类FrameLayout可知:
1
public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks
调用super.dispatchTouchEvent(event)即调用FrameLayout.dispatchTouchEvent(event)
2.5 小结
三、 Activity.onTouchEvent()
3.1 onTouchEvent()
ViewGroup和View都没有消费事件,该事件最终回到Activity,并交给Activity.onTouchEvent()。
1
2
3
4
5
6
7
8
9
public boolean onTouchEvent(MotionEvent event) {
if (mWindow.shouldCloseOnTouch(this, event)) {
finish();
return true;
}
// activity也不消费事件,事件分发流程终结
return false;
}
3.2 Windos.shouldCloseOnTouch()
事件点击在DecorView外且点击事件没有被其他组件消费时,支持关闭Activity
1
2
3
4
5
6
7
8
9
10
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的setter
1
2
3
4
5
6
private boolean mCloseOnTouchOutside = false;
public void setCloseOnTouchOutside(boolean close) {
mCloseOnTouchOutside = close;
mSetCloseOnTouchOutside = true;
}
mSetCloseOnTouchOutside的setter
1
2
3
4
5
6
7
8
private boolean mSetCloseOnTouchOutside = false;
public void setCloseOnTouchOutsideIfNotSet(boolean close) {
if (!mSetCloseOnTouchOutside) {
mCloseOnTouchOutside = close;
mSetCloseOnTouchOutside = true;
}
}
检查点击事件是否落在DecorView外
1
2
3
4
5
6
7
8
9
private boolean isOutOfBounds(Context context, MotionEvent event) {
final int x = (int) event.getX();
final int y = (int) event.getY();
final int slop = ViewConfiguration.get(context).getScaledWindowTouchSlop();
final View decorView = getDecorView();
return (x < -slop) || (y < -slop)
|| (x > (decorView.getWidth()+slop))
|| (y > (decorView.getHeight()+slop));
}
3.3 window.peekDecorView()
获取当前已经创建的顶层decorView。已知Window是抽象类,所以看实现类PhoneWindow
1
public abstract View peekDecorView();
3.4 PhoneWindow.peekDecorView()
返回持有的DecorView
1
2
3
4
5
6
private DecorView mDecor;
@Override
public final View peekDecorView() {
return mDecor;
}
四、总结
Activity仅有 dispatchTouchEvent() 和 onTouchEvent() 两个主要方法,和View一样没有 onInterceptTouchEvent()。
因为Activity本身默认不处理任何点击事件,只在ViewGroup和View都不处理事件时才尝试消费事件。最终也可能返回false,表示Activity也不消费事件。