请尊重原创,转载请注明出处【tianyl_Melody】的博客
在Android中,我们经常被问到的一个问题就是事件分发,关于事件分发,感觉不论是谁都能够说那么两句,但是如果一到应用中,就又经常遇到奇奇怪怪的问题,今天,就从一个简单的事件分发的问题说起
1、onTouch和onClick
如果一个View我们同时设置onTouch和onClick的监听会怎么样呢,按照我们的想法,那就是触摸的时候调用onTouch方法,然后点击的时候,就调用onClick方法,
于是写下这两个方法:
@Override
public boolean onTouch(View v, MotionEvent event) {
Toast.makeText(getApplicationContext(),"onTouch",Toast.LENGTH_SHORT).show();
return true;
}
@Override
public void onClick(View v) {
Toast.makeText(getApplicationContext(),"onClick",Toast.LENGTH_SHORT).show();
}
分别试着在onTouch方法中返回true和false,奇怪的事情发生了,当返回true的时候,并没有调用到onClick方法,为什么会这样呢,接下来,就从源码中寻找答案。
首先,这肯定是事件分发搞的鬼,所以去dispatchTouchEvent寻找答案,然后我们寻找onTouch方法,看onTouch返回值不同会有什么影响:
public boolean dispatchTouchEvent(MotionEvent event) {
......
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;
}
如果li.mOnTouchListener.onTouch(this, event)返回结果为true,那么result就为true,结果不会调用到view中的onTouchEvent方法。
而mOnTouchListener这个则是我们自己设置的
public void setOnTouchListener(OnTouchListener l) {
getListenerInfo().mOnTouchListener = l;
}
而这个判断中
if (!result && onTouchEvent(event))
如果result为true,那么就不会执行onTouchEvent这个方法;
在onTouchEvent方法中
if (!post(mPerformClick)) {
performClick();
}
会通过performClick调用到onClick这个方法。
public boolean performClick() {
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);
return result;
}
这里的li.mOnClickListener.onClick(this),也是我们设置的
public void setOnClickListener(@Nullable OnClickListener l) {
if (!isClickable()) {
setClickable(true);
}
getListenerInfo().mOnClickListener = l;
}
2、Activity的事件分发
接下来,就从Activity的点击开始说起
Activity.java
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
由native层调用到Activity的dispatchTouchEvent方法,然后在Activity中判断
如果getWindow().superDispatchTouchEvent(ev)返回true,那么就不会调用到Activity自己的onTouchEvent方法。
getWindow拿到的是PhoneWindow对象,调用的是PhoneWindow中的superDispatchTouchEvent方法。
PhoneWindow.java
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
在PhoneWindow中调用的是mDecor的superDispatchTouchEvent的方法,而在mDecor的superDispatchTouchEvent方法中,又是调用的父类的dispatchTrackballEvent方法。
class DecorView
public boolean superDispatchTrackballEvent(MotionEvent event) {
return super.dispatchTrackballEvent(event);
}
而DecorView其实是继承的FrameLayout的
private final class DecorView extends FrameLayout
而FrameLayout并没有实现dispatchTrackballEvent方法,其实最终调用到的,还是ViewGroup的dispatchTrackballEvent方法。
3、ViewGroup
前两行是辅助功能,这里略过,在事件分发中,首先会进行安全检查
if (onFilterTouchEventForSecurity(ev))
然后在down事件中进行初始化
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Throw away all previous state when starting a new touch gesture.
// The framework may have dropped the up or cancel event for the previous gesture
// due to an app switch, ANR, or some other state change.
cancelAndClearTouchTargets(ev);
resetTouchState();
}
在cancelAndClearTouchTargets方法中,如果不是第一次点击事件(例如之前已经点击过一次),mFirstTouchTarget则不为null,那么会清空之前的点击状态,其中有一个clearTouchTargets方法:
private void clearTouchTargets() {
TouchTarget target = mFirstTouchTarget;
if (target != null) {
do {
TouchTarget next = target.next;
target.recycle();
target = next;
} while (target != null);
mFirstTouchTarget = null;
}
}
会清空这个单向链表,并制空。
在resetTouchState方法中,会重置所有的触摸状态,为新的触摸事件做准备。
private void resetTouchState() {
clearTouchTargets();
resetCancelNextUpFlag(this);
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
mNestedScrollAxes = SCROLL_AXIS_NONE;
}
clearTouchTargets方法,之前已经调用过,会清除mFirstTouchTarget的值,resetCancelNextUpFlag方法则会清除一些标志位。
接下来有一个变量
final boolean intercepted;
保存是否拦截的标志。
如果调用过requestDisallowInterceptTouchEvent(true)这个方法
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;
} else {
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
}
// Pass it up to our parent
if (mParent != null) {
mParent.requestDisallowInterceptTouchEvent(disallowIntercept);
}
}
那么mGroupFlags会保存FLAG_DISALLOW_INTERCEPT这个标志位。
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
此时disallowIntercept为true。
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
那么就不会执行onInterceptTouchEvent(ev)这个方法。
以上,就是一个Activity的点击事件,调用到ViewGroup的整个过程。
简要总结一下:
1、当事件点击到Activity上时,首先会由底层传递到Activity中,调用dispatchTouchEvent方法。
2、在Activity的dispatchTouchEvent方法中,会调用到getWindow().superDispatchTouchEvent(ev),这里的getWindow拿到的就是PhoneWindow。
3、在getWindow的superDispatchTouchEvent中,会调用到mDecor.superDispatchTouchEvent。
4、因为mDecor是继承的FrameLayout的,所以这里其实就是调用的ViewGroup的dispatchTouchEvent。
到这里,基本理清了,一个点击事件点到Activity上面,是如何传递过来的,搞清楚了从哪里来,接下来就需要理清这个事件到哪里去(篇幅有限,见下一篇)