事件分发机制有多重要就不多说了,处理滑动冲突时,这是必备的基础知识!写下这篇文章的目的是确定自己是否真的理解,有助于加深印象以及了解自身理解状况吧,算是一份读了几篇文章之后的读后感。(ps:2019-4-30距离第一次写也才20多天的时间,回想事件分发的细节竟然一时想不起来,回来看到这个,顿时感觉写文章好有意义!而且自己写的文章,很符合自己的口味,读起来很舒服呀~一气呵成!^_^)
分发的对象是touch事件,touch事件有4种:
MotionEvent.ACTION_DOWN 按下View(所有事件的开始)
MotionEvent.ACTION_UP 抬起View(与DOWN对应)
MotionEvent.ACTION_MOVE 滑动View
MotionEvent.ACTION_CANCEL 结束事件(非人为原因)
一个touch事件分发传递的流程是:activity->window->viewgroup->view 记!记!记!
方法只有三个:dispatchTouchEvent() 、onInterceptTouchEvent()和onTouchEvent()。
老实说现在源码看的一脸懵逼,复制份大佬的伪代码讲解:
这三个方法的联系可以用下面的伪代码来表示:
点击事件产生后,会直接调用dispatchTouchEvent()方法
public boolean dispatchTouchEvent(MotionEvent ev) {
//代表是否消耗事件
boolean consume = false;
if (onInterceptTouchEvent(ev)) {
//如果onInterceptTouchEvent()返回true则代表当前View拦截了点击事件
//则该点击事件则会交给当前View进行处理
//即调用onTouchEvent ()方法去处理点击事件
consume = onTouchEvent (ev) ;
} else {
//如果onInterceptTouchEvent()返回false则代表当前View不拦截点击事件
//则该点击事件则会继续传递给它的子元素
//子元素的dispatchTouchEvent()就会被调用,重复上述过程
//直到点击事件被最终处理为止
consume = child.dispatchTouchEvent (ev) ;
}
return consume;
}
总结整理下:
先判断是否拦截,拦截了就去调用viewgroup自己的onTouchEvent事件,并将返回值作为本次dispatchTouchEvent的返回值。
没拦截就去找自己的子view,把子view的dispatchTouchEvent的返回值作为自己的返回值。
public boolean dispatchTouchEvent(MotionEvent event) {
if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
mOnTouchListener.onTouch(this, event)) {
return true;
}
return onTouchEvent(event); }
总结整理:
子view的dispatchTouchEvent事件先来3个判断:
1.touchListener是否不为空
2.该view是否可用
3.touchListener.onTouch的返回值为true?
这三个都成立,则子view返回true。
如果有一个不成立,则执行onTouchEvent,并将该返回的返回值作为子View的dispatchTouchEvent的返回值。
结合上面view的dispatchTouchEvent代码分析,可以发现,我们是先去判断TouchListener.touch的返回值,即我们先执行了该方法,如果返回false,那么子view的dispatchTouchEvent会执行onTouchEvent方法,在该方法中,我们会执行performClick方法。
看下代码~:
public boolean onTouchEvent(MotionEvent event) {
final int viewFlags = mViewFlags;
if ((viewFlags & ENABLED_MASK) == DISABLED) {
// A disabled view that is clickable still consumes the touch
// events, it just doesn't respond to them.
return (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));
}
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
//如果该控件是可以点击的就会进入到下两行的switch判断中去;
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
//如果当前的事件是抬起手指,则会进入到MotionEvent.ACTION_UP这个case当中。
switch (event.getAction()) {
case MotionEvent.ACTION_UP:
boolean prepressed = (mPrivateFlags & PREPRESSED) != 0;
// 在经过种种判断之后,会执行到关注点1的performClick()方法。
//请往下看关注点1
if ((mPrivateFlags & PRESSED) != 0 || prepressed) {
// take focus if we don't have it already and we should in
// touch mode.
boolean focusTaken = false;
if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
focusTaken = requestFocus();
}
if (!mHasPerformedLongPress) {
// This is a tap, so remove the longpress check
removeLongPressCallback();
// 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)) {
//关注点1
//请往下看performClick()的源码分析
performClick();
}
}
}
if (mUnsetPressedState == null) {
mUnsetPressedState = new UnsetPressedState();
}
if (prepressed) {
mPrivateFlags |= PRESSED;
refreshDrawableState();
postDelayed(mUnsetPressedState,
ViewConfiguration.getPressedStateDuration());
} else if (!post(mUnsetPressedState)) {
// If the post failed, unpress right now
mUnsetPressedState.run();
}
removeTapCallback();
}
break;
case MotionEvent.ACTION_DOWN:
if (mPendingCheckForTap == null) {
mPendingCheckForTap = new CheckForTap();
}
mPrivateFlags |= PREPRESSED;
mHasPerformedLongPress = false;
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
break;
case MotionEvent.ACTION_CANCEL:
mPrivateFlags &= ~PRESSED;
refreshDrawableState();
removeTapCallback();
break;
case MotionEvent.ACTION_MOVE:
final int x = (int) event.getX();
final int y = (int) event.getY();
// Be lenient about moving outside of buttons
int slop = mTouchSlop;
if ((x < 0 - slop) || (x >= getWidth() + slop) ||
(y < 0 - slop) || (y >= getHeight() + slop)) {
// Outside button
removeTapCallback();
if ((mPrivateFlags & PRESSED) != 0) {
// Remove any future long press/tap checks
removeLongPressCallback();
// Need to switch from pressed to not pressed
mPrivateFlags &= ~PRESSED;
refreshDrawableState();
}
}
break;
}
//如果该控件是可以点击的,就一定会返回true
return true;
}
//如果该控件是可以点击的,就一定会返回false
return false;
}
可以发现在ACTION_UP中,调用了performClick()方法,
在该源码中,如下图,可以看到先判断onCLickListener是否为空,不为空就调用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);
notifyEnterOrExitForAutoFillIfNeeded(true);
return result;
}
简单总结:view的dispatchTouchEvent中,先调用了touch方法,该方法返回true,并且控件可用,则dispatchTouchEvent直接返回true,不执行onTouchEvent方法(在该方法中才调用到OnClick),即如果touch方法返回true就屏蔽了onclick方法,touch方法的优先级是高于onclick方法的。
参考:https://blog.csdn.net/Simon_Crystin/article/details/78656113
https://www.jianshu.com/p/38015afcdb58