前言
在分析事件分发机制之前,我们先从广义上了解几个函数的作用
dispatchTouchEvent 这个函数可以认为是事件分发的总入口;
onInterceptTouchEvent 事件拦截的函数,只在ViewGroup中有,view没有此函数;
onTouchEvent 可以认为是事件处理的函数;
dispatchTransformedTouchEvent 事件分发的函数,一般是在dispatchTouchEvent中调用,此函数主要处理两种情况的分发,一种是分发给子view,一种是分发给当前view/viewgroup;
requestDisallowInterceptTouchEvent 请求父viewgroup不要拦截事件,一般在子view中调用,一般的使用方法是在子view的dispatchTouchEvent中调用getParent.requestDisallowInterceptTouchEvent(true);
一、事件分发流程–ViewGroup部分
1、Activity -> dispatchTouchEvent
如下所示,事件分发的入口是从Activity的dispatchTouchEvent方法开始的;
注释1处表示如果是down事件,首先执行onUserInteraction方法,这个方法在Activity中是一个空实现,我们可以在自己的Activity中重写此方法,然后监听down事件,一个应用就是可以在此处埋点统计;
注释2处调用getWindow获得是PhoneWindow,因此实际调用的是phoneWindow的superDispatchTouchEvent;如果注释2处返回为false,也就是事件没有处理,注释3处就会调用Activity的onTouchEvent;
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction(); //1
}
if (getWindow().superDispatchTouchEvent(ev)) { //2
return true;
}
return onTouchEvent(ev); //3
}
复制代码
2、PhoneWindow -> superDispatchTouchEvent
接着看phoneWindow的superDispatchTouchEvent,注释1处表示又调用了DecorView的superDispatchTouchEvent;注释2处表示又调用了ViewGroup的dispatchTouchEvent;
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event); //1
}
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event); //2
}
复制代码
3、ViewGroup -> dispatchTouchEvent
接着看ViewGroup的dispatchTouchEvent,这个方法比较长,包含了主要的分发逻辑;
注释1处表示当事件是点击事件时,会把TouchTarget清空,然后把TouchState重置;
注释2处的disallowIntercept变量通过FLAG_DISALLOW_INTERCEPT标志位设置,那么这个标志位在哪里设置呢?就是在requestDisallowInterceptTouchEvent方法中设置的,当子view调用这个方法请求父view不要拦截时,就不会进入注释3处的onInterceptTouchEvent方法,从而也就不会拦截了;
这里我们可能会有一个疑问,事件分发是从上层分发到下层的,那父view直接把事件拦截了,子view如何去请求父view不要拦截?其实事件分发下来之后down事件一般都是没有拦截的,然后就会进入view的diapatchTouchEvent,我们就可以在这个方法中调用方法请求父view不要拦截,这样当move事件来了之后,因为标志位设置为了true,即使在viewgroup的onInterceptTouchEvent方法中进行了拦截,也不会进入viewgroup的onInterceptTouchEvent方法了,从而不能对事件进行拦截;
注释3处的onInterceptTouchEvent用于父view拦截事件,一般在父viewgroup中不拦截down事件,然后move事件来了之后在这个方法中判断是否需要拦截,如果需要拦截就会返回true,同时发送一个cancel事件给子view,然后后续的move事件不会再继续流向子view了;
注释4和注释5处的dispatchTransformedTouchEvent用于向子view或者自身分发事件;后面会分析;
public boolean dispatchTouchEvent(MotionEvent ev) {
、、、
boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
// Handle an initial down.
if (actionMasked == MotionEvent.ACTION_DOWN) {
cancelAndClearTouchTargets(ev); //1
resetTouchState();
}
// Check for interception.
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) { //2
intercepted = onInterceptTouchEvent(ev); //3
ev.setAction(action);
} else {
intercepted = false;
}
} else {
intercepted = true;
}
TouchTarget newTouchTarget = null;
boolean alreadyDispatchedToNewTouchTarget = false;
if (!canceled && !intercepted) {
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
final int childrenCount = mChildrenCount;
if (newTouchTarget == null && childrenCount != 0) {
final View[] children = mChildren;
for (int i = childrenCount - 1; i >= 0; i--) {
、、、
//4
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
、、、
}
}
if (preorderedList != null) preorderedList.clear();
}
}
}
// Dispatch to touch targets.
if (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary view.
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS); //5
} else {
、、、
}
}
return handled;
}
复制代码
4、ViewGroup -> dispatchTransformedTouchEvent
注释1和注释2处主要处理cancel事件的分发;当child不为null时调用注释2处将cancel事件分发给子view;
注释3和注释4用于正常事件流程的分发;当child为空时,调用注释3处,这里的super就代表的View;当child不为空时,调用注释4处,这里的child如果还是ViewGroup类型,则又会进入ViewGroup的dispatchTouchEvent,继续递归调用分发,如果这个child是View类型的,则会进入View的dispatchTouchEvent;
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
final int oldAction = event.getAction();
if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
event.setAction(MotionEvent.ACTION_CANCEL);
if (child == null) {
handled = super.dispatchTouchEvent(event); //1
} else {
handled = child.dispatchTouchEvent(event); //2
}
event.setAction(oldAction);
return handled;
}
// Perform any necessary transformations and dispatch.
if (child == null) {
handled = super.dispatchTouchEvent(transformedEvent); //3
} else {
、、、
handled = child.dispatchTouchEvent(transformedEvent); //4
}
// Done.
transformedEvent.recycle();
return handled;
}
复制代码
5、ViewGroup -> requestDisallowInterceptTouchEvent
当子view请求viewgroup不要拦截事件时会调用如下方法,注释1处和注释2处分别进行的处理;就是将mGroupFlags变量置为对应的标志位;
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) {
// We're already in this state, assume our ancestors are too
return;
}
if (disallowIntercept) {
mGroupFlags |= FLAG_DISALLOW_INTERCEPT; //1
} else {
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT; //2
}
}
复制代码
6、ViewGroup -> onInterceptTouchEvent
ViewGroup的onInterceptTouchEvent方法如下所示,可见viewGroup基本不会拦截事件,正常情况的事件分发基本都是返回false,所以RecyclerView、ScrollView等等这些滚动容器一般都是重写的onInterceptTouchEvent,然后在move事件中判断是否拦截;
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (ev.isFromSource(InputDevice.SOURCE_MOUSE)
&& ev.getAction() == MotionEvent.ACTION_DOWN
&& ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY)
&& isOnScrollbarThumb(ev.getX(), ev.getY())) {
return true;
}
return false;
}
复制代码
二、事件分发流程–View部分
1、View -> dispatchTouchEvent
当事件分发到View时,会进入View的dispatchTouchEvent方法,注释1处会首先调用onTouch方法,如果onTouch返回的true,则在注释2处判断result为true,从而不会进入onTouchEvent方法;那么这个onTouch方法是在哪设置的呢?就是调用View的setOnTouchListener方法,注释3所示;
public boolean dispatchTouchEvent(MotionEvent event) {
、、、
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)) { //1
result = true;
}
if (!result && onTouchEvent(event)) { //2
result = true;
}
}
、、、
return result;
}
public void setOnTouchListener(OnTouchListener l) {
getListenerInfo().mOnTouchListener = l; //3
}
复制代码
2、View -> onTouchEvent
View的onTouchEvent如下所示,我们主要关注performClickInternal方法;
注释1处表示clickable如果为true会进入,这个clickable是通过setOnClickListener方法设置,因此我们如果设置了view的setOnClickListener,就会进入注释2处的performClickInternal方法,在这里面会执行onClick方法,我们在后面会分析;
然后注释3处会返回true代表事件被消费;还有一个需要注意的点,就是performClickInternal方法是在MotionEvent.ACTION_UP分支内执行的,也就是只有up事件才会触发;
public boolean onTouchEvent(MotionEvent event) {
、、、
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) { //1
switch (action) {
case MotionEvent.ACTION_UP:
、、、
if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
// take focus if we don't have it already and we should in
// touch mode.
、、、
if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
// Only perform take click actions if we were in the pressed state
if (!focusTaken) {
// Use a Runnable and post this rather than calling
// performClick directly. This lets other visual state
// of the view update before click actions start.
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
performClickInternal(); //2
}
}
}
}
mIgnoreNextUpEvent = false;
break;
case MotionEvent.ACTION_DOWN:
、、、
break;
case MotionEvent.ACTION_CANCEL:
、、、
break;
case MotionEvent.ACTION_MOVE:
、、、
break;
}
return true; //3
}
return false;
}
public void setOnClickListener(@Nullable OnClickListener l) {
if (!isClickable()) {
setClickable(true);
}
getListenerInfo().mOnClickListener = l;
}
复制代码
3、View -> performClickInternal
View的performClickInternal的调用链如下所示,注释2处我们可以看到最终会调用OnClickListener的onClick方法,也就是我们经常调用的setOnClickListener中传入的listener,进而调用其中的onClick;
private boolean performClickInternal() {
// Must notify autofill manager before performing the click actions to avoid scenarios where
// the app has a click listener that changes the state of views the autofill service might
// be interested on.
notifyAutofillManagerOnClick();
return performClick(); //1
}
public boolean performClick() {
final boolean result;
final ListenerInfo li = mListenerInfo;
if (li != null && li.mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
li.mOnClickListener.onClick(this); //2
result = true;
} else {
result = false;
}
、、、
return result;
}
复制代码