基础概念
- 触摸事件:手指触摸屏幕时生成的事件,即
MotionEvent
。常见的触摸事件有:ACTION_DOWN
、ACTION_MOVE
、ACTION_UP
以及ACTION_CANCEL
,当存在多个手指触摸屏幕时,还会触发ACTION_POINTER_DOWN
和ACTION_POINTER_UP
事件。 - 焦点事件:
ACTION_DOWN
和ACTION_POINTER_DOWN
属于焦点事件,通过MotionEvent
中的PointerId
进行描述; - 触摸事件序列:从手指触摸屏幕开始到手指离开屏幕结束的过程中触发的一系列事件,通常以
ACTION_DOWN
事件开始、ACTION_UP
事件结束,中间有不定数量的ACTION_MOVE
、ACTION_POINTER_DOWN
或者ACTION_POINTER_UP
事件的一系列事件。 - 滑动冲突:
View
树中相邻的层级上均存在可滑动的View
,当用户触摸屏幕时触发了ACTION_MOVE
事件导致有多个View
可以处理的情况。 - 事件分发机制:触摸屏幕产生的事件
MotionEvent
在整个View
树上分发处理的逻辑,理解事件分发机制的实现原理才能知道如何解决View
滑动冲突问题。
源码分析
事件分发流程
当用户开始触摸手机屏幕时,经过传感器等一系列硬件处理,最终生成触摸事件并由SystemServer
进程的InputMangerService
服务通过Socket
发送到目标应用进程,经过多个的InputStage
分发之后触摸事件被传递到DecorView#dispatchPointerEvent
方法,dispatchPointerEvent
方法内部调用了dispatchTouchEvent
方法,通过PhoneWindow#getCallback
方法拿到目标Activity
,最终调用Activity#dispatchTouchEvent
方法进行处理。
public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource {
public final boolean dispatchPointerEvent(MotionEvent event) {
if (event.isTouchEvent()) {
return dispatchTouchEvent(event);
} else {
return dispatchGenericMotionEvent(event);
}
}
}
public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
// mWindow是PhoneWindow,cb是Activity
final Window.Callback cb = mWindow.getCallback();
return cb != null && !mWindow.isDestroyed() && mFeatureId < 0
? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev);
}
}
public class Activity extends ContextThemeWrapper
implements LayoutInflater.Factory2,
Window.Callback, KeyEvent.Callback,
OnCreateContextMenuListener, ComponentCallbacks2,
Window.OnWindowDismissedCallback,
ContentCaptureManager.ContentCaptureClient {
/**
* 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.
*
* @see #onTouchEvent(MotionEvent)
*/
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
// 通过Window进行分发最终交由根布局DecorView进行处理,如果处理成功则将事件从队列中移除,否则交由onTouchEvent继续处理;
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
// 如果整个View树都没有处理触摸事件,则由Activity的onTouchEvent处理
return onTouchEvent(ev);
}
/**
* Retrieve the current {@link android.view.Window} for the activity.
* This can be used to directly access parts of the Window API that
* are not available through Activity/Screen.
*
* @return Window The current window, or null if the activity is not
* visual.
*/
public Window getWindow() {
return mWindow;
}
final void attach(Context context, ActivityThread aThread,
Instrumentation instr, IBinder token, int ident,
Application application, Intent intent, ActivityInfo info,
CharSequence title, Activity parent, String id,
NonConfigurationInstances lastNonConfigurationInstances,
Configuration config, String referrer, IVoiceInteractor voiceInteractor,
Window window, ActivityConfigCallback activityConfigCallback, IBinder assistToken,
IBinder shareableActivityToken) {
attachBaseContext(context);
mFragments.attachHost(null /*parent*/);
mActivityInfo = info;
mWindow = new PhoneWindow(this, window, activityConfigCallback);
// 省略其他代码
}
}
接着,触摸事件会被分发给目标Activity
的PhoneWindow
(在Activity
实例创建之后调用attach
方法中进行创建)进行处理。经过PhoneWindow#superDispatchTouchEvent
方法将触摸事件交由DecorView.superDispatchTouchEvent
。因为DecorView
继承自FrameLayout
,而FrameLayout
继承自ViewGroup
,所以触摸事件最终由ViewGroup.dispatchTouchEvent
开始分发处理。
public class PhoneWindow extends Window implements MenuBuilder.Callback {
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
private void installDecor() {
mForceDecorInstall = false;
if (mDecor == null) {
mDecor = generateDecor(-1);
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
}
} else {
mDecor.setWindow(this);
}
// 省略其他代码
}
}
public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
}
至此,触摸事件已经被分发到View
树的根节点,开始在View
树上进行遍历(深度遍历)和分发处理,总结流程图如下:
为了侧重分析触摸事件的分发流程,下面主要围绕关键代码进行分析,和事件分发流程关联不大的部分直接略过;
public abstract class ViewGroup extends View implements ViewParent, ViewManager {
// First touch target in the linked list of touch targets.
// 记录当前父View下第一个处理触摸事件的View对象,内部通过链表的形式进行维护
@UnsupportedAppUsage
private TouchTarget mFirstTouchTarget;
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean handled = false;
// 过滤不安全的触摸事件
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction(