关于事件分发,从三个函数开始:
1、dispatchTouchEvent 事件分发
2、onInterceptTouchEvent 事件拦截
3、onTouchEvent 事件消费
这三个函数主要体现在哪?
Activity、ViewGroup、View;(其实还有PhoneWindow 、DecorView 很少用到,我就不混淆视听了)
Q:View的事件分发从哪里开始到哪里结束?
从Activity->ViewGroup->View;这是传递路线;事件在哪消费,就在哪结束;如果最后到View都没有被消费;会按逆序再传递到上
去,View->ViewGroup->Activity;如果最后都没有被消费,那么事件就会被丢弃;
Q:事件分发跟开始提的三个函数什么关系?
哈哈,刚开始提出的三个函数是对应事件分发的具体函数;如:Activity对事件的处理,三步处理;即:Activity收到事件,开始分
发将调用函数dispatchTouchEvent,如果Activity需要对这件事处理,那么在dispatchTouchEvent就会返回true,即把事件消费,
然后调用onInterceptTouchEvent事件拦截下来,,并返回true(如果Activity不拦截,也会调用onInterceptTouchEvent,但是返回的
是false),既然把事件拦截下来了,那么久调用onTouchEvent进行处理;(View没有onInterceptTouchEvent事件拦截,为什
么???可想而知啊,View已经是事件最后传递的对象了,是没有权利拦截消息的;就是说一个小小的码农是不能够把上级的指
示隐瞒不报的,有道理吗)!!
这个有什么难度吗?很好理解吧
Q:为什么View有事件dispatchTouchEvent分发?这家伙分给谁?
哎哟喂,好问题;ViewGroup有事件分发,可以理解,毕竟还需要分给子View;但是View为什么有啊;这也太皮了吧???
想想,还漏了啥?想到了吧!!!没错,就是onClick、onLongClick、onTouch、onTouchEvent (View本身的);
Q:那点击事件怎么触发、谁先触发,顺序是什么?怎么处理的?
这个问题,我们截一段源码看一下;
public boolean dispatchTouchEvent(MotionEvent event) {
// If the event should be handled by accessibility focus first.
if (event.isTargetAccessibilityFocus()) {
// We don't have focus or no virtual descendant has it, do not handle the event.
if (!isAccessibilityFocusedViewOrHost()) {
return false;
}
// We have focus and got the event, then use normal event dispatch.
event.setTargetAccessibilityFocus(false);
}
//上面的代码,意思是如果View不可点击,那么直接就不处理了,有很多情况会使View可点击,比如设置了onClick、onLongClick...
boolean result = false;
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(event, 0);
}
final int actionMasked = event.getActionMasked();
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Defensive cleanup for new gesture
stopNestedScroll();
}
if (onFilterTouchEventForSecurity(event)) {
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
//这里的if,大概意思是,如果View设置了ontouchlistener并且还返回了true,则消费该事件
}
if (!result && onTouchEvent(event)) {
result = true;
//如果用户设置的OnTouchListener没有返回true,那么就叫给View的onTouchEvent来处理
}
}
if (!result && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
}
// Clean up after nested scrolls if this is the end of a gesture;
// also cancel it if we tried an ACTION_DOWN but we didn't want the rest
// of the gesture.
if (actionMasked == MotionEvent.ACTION_UP ||
actionMasked == MotionEvent.ACTION_CANCEL ||
(actionMasked == MotionEvent.ACTION_DOWN && !result)) {
stopNestedScroll();
}
return result;
}
所以顺序是什么?结果:用户为View设置的onTouchListener先执行再到View的onTouchEvent??点击事件呢??MMP?
Q:点击事件到底在哪处理的?
答案在onTouchEvent,再去源码里面看看:
public boolean onTouchEvent(MotionEvent event) {
final float x = event.getX();
final float y = event.getY();
final int viewFlags = mViewFlags;
if ((viewFlags & ENABLED_MASK) == DISABLED) {
//如果View不可用,但是还有可能消耗事件,比如:TextView,是不可点击的,但是设置onClick就会消耗事件
if (event.getAction() == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
//清除一些状态
}
// 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));
//判断是否被onClick或onLongClick消费
}
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
//判断是否在设置的扩大点击范围内消费事件
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
switch (event.getAction()) {
case MotionEvent.ACTION_UP:
boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
if ((mPrivateFlags & PFLAG_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 (prepressed) {
// The button is being released before we actually
// showed it as pressed. Make it show the pressed
// state now (before scheduling the click) to ensure
// the user sees it.
setPressed(true, x, y);
}
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)) {
performClick(); //触发点击事件
}
}
}
if (mUnsetPressedState == null) {
mUnsetPressedState = new UnsetPressedState();
}
if (prepressed) {
postDelayed(mUnsetPressedState,
ViewConfiguration.getPressedStateDuration());
} else if (!post(mUnsetPressedState)) {
// If the post failed, unpress right now
mUnsetPressedState.run();
}
removeTapCallback();
}
break;
case MotionEvent.ACTION_DOWN:
mHasPerformedLongPress = false;
if (performButtonActionOnTouchDown(event)) { //过滤鼠标右键
break;
}
// Walk up the hierarchy to determine if we're inside a scrolling container.
boolean isInScrollingContainer = isInScrollingContainer();
//判断View是否是可滑动的
// For views inside a scrolling container, delay the pressed feedback for
// a short period in case this is a scroll.
if (isInScrollingContainer) {
//滚动视图
mPrivateFlags |= PFLAG_PREPRESSED;
if (mPendingCheckForTap == null) {
mPendingCheckForTap = new CheckForTap();
}
mPendingCheckForTap.x = event.getX();
mPendingCheckForTap.y = event.getY();
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
} else {
// Not inside a scrolling container, so show the feedback right away
setPressed(true, x, y);
checkForLongClick(0);
//刷新按下状态,并开始检查长按事件(倒计时触发长按,下面贴出源码,可以看下)
}
break;
case MotionEvent.ACTION_CANCEL:
setPressed(false);
removeTapCallback();
removeLongPressCallback();
break;
case MotionEvent.ACTION_MOVE:
drawableHotspotChanged(x, y);
// Be lenient about moving outside of buttons
if (!pointInView(x, y, mTouchSlop)) {
// Outside button
removeTapCallback();
if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
// Remove any future long press/tap checks
removeLongPressCallback();
setPressed(false);
}
}
break;
}
return true;
}
return false;
}
看注释!!
再贴一段长按探针代码
private void checkForLongClick(int delayOffset) {
if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) {
mHasPerformedLongPress = false;
if (mPendingCheckForLongPress == null) {
mPendingCheckForLongPress = new CheckForLongPress();
}
mPendingCheckForLongPress.rememberWindowAttachCount();
postDelayed(mPendingCheckForLongPress,
ViewConfiguration.getLongPressTimeout() - delayOffset);
}
}
所以总结一下?
View(不要和ViewGroup混淆了)的事件消费从dispatchTouchEvent开始,判断OnTouchListener会不会消费,不消费则判断onTouchEvent是否消费,onTouchEvent里面会判断onClick和onLongClick事件是否消费;如果都没有消费,则逆向传递向上传递;
View是没有拦截事件能力,除非你想做苦力,把事件处理掉,要不然就逆向上报;
这篇被写的乱七八糟;大概写了事件传递的开始和结束;然后着重讲了一下View的事件处理过程;