Andrioid View与ViewGroup事件传递机制从现象到源码分析。
关于View与ViewGroup相关系列之知识结构梳理。今天就从View与ViewGroup事件传递作为起始点,后续相关系列持续更新。
View的事件传递
关于View事件传递主要涉及dispatchTouchEvent(分发)、View本身的OnTouch监听以及OnTouchEvent还有OnClick事件。这四者之间的关系。
- dispatchTouchEvent ,View本身是否需要处理事件,ture为分发,false则不分发;
- OnTouch 即View本身的OnTouchListenerListener,当dispatchTouchEvent返回false或super(即事件向下分发)会触发OnTouch事件返回false则表示不消耗事件向下传递,返回true即消耗事件,停止向下传递向上反馈事件消耗;
- OnTouchEvent当OnTouch 返回false时事件会传递至OnTouchEvent询问事件消耗;
- OnCLick当OnTouchEvent执行UP事件时会触发OnClick事件 ;
不同的返回值会导致事件传递流程相差甚远,通过不断修改这些方法的返回值并查看日志记录,我们最终可以得到屏幕按下操作ACTION_DOWN事件的处理流程。
触摸事件从dispatchTouchEvent开始,如果不进行人为干预(默认返回super),则事件将会依照嵌套层次从外层向内层传递,到达最内层的View时View的OntouchEvent事件返回true即为消耗,返回super或false则事件会重新向外层传递,由最外层View的OnTouchEvent消费。
当View的dispatchTouchEvent返回false即表示View自身的OnTouch/OnTouchEvent都返回false;(表示没有消费,事件逐级像上传递由最外层的Activity的OnTOuchEvent消费)。返回true则表示事件被View消费。
通过源码看View
当事件传递到View中时,首先进入View的dispatchTouchEvent
/**
* Pass the touch screen motion event down to the target view, or this
* view if it is the target.
*
* 如果事件能够传递给当前View,那么此方法一定会被调用,
* 返回结果受当前View的onTouchEvent和下级View的dispatchTouchEvent方法的影响,表示是否消耗当前事件。
* View是一个单独的元素,它没有子元素因此无法向下传递事件,
* 所以它只能自己处理事件,所以View的onTouchEvent方法默认返回true。
*
* @param event The motion event to be dispatched.
* @return True if the event was handled by the view, false otherwise.
*/
public boolean dispatchTouchEvent(MotionEvent event) {
if (onFilterTouchEventForSecurity(event)) {
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;
// 首先会判断当前View有没有设置OnTouchListener,
// 如果OnTouchListener中的onTouch方法返回true,那么onTouchEvent方法就不会被调用,
// 可见OnTouchListener的优先级要高于onTouchEvent,这样做的好处是方便在外界处理点击事件。
if (li != null && li.mOnTouchListener != null //1
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
if (!result && onTouchEvent(event)) { //2
result = true;
}
}
return result;
}
源码1处可见OnTouchListener的优先级要高于onTouchEvent,当OnTouchListener返回true,则OnTouchEvent不会被触发。
源码2处可见当 OnTouchListener返回false,则会执行OnTouchEvent方法
View的onTouchEvent:
public boolean onTouchEvent(MotionEvent event) {
// 对点击事件的具体处理。只要CLICKABLE和LONG_CLICKABLE有一个为true,那么它就会消耗这个事件,因为返回了true
// View的LONG_CLICKABLE属性默认为false,而CLICKABLE属性默认为true,不过具体的View的CLICKABLE又不一定,
// 确切来说是可点击的View其CLICKABLE属性true,比如Button,不可点击的View的CLICKABLE为false,比如TextView。
// 通过setClickable和setLongClickable可以设置这两个属性。
// 另外setOnClickListener和setOnLongClickListener会自动将View的这两个属性设为true。
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
case MotionEvent.ACTION_UP:
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
if ((viewFlags & TOOLTIP) == TOOLTIP) {
handleTooltipUp();
}
if (!clickable) {
removeTapCallback();
//移除长按监测
removeLongPressCallback();
mInContextButtonPress = false;
mHasPerformedLongPress = false;
mIgnoreNextUpEvent = false;
break;
}
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 && !mIgnoreNextUpEvent) {
// 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.
// 当up事件发生时,会触发performClick方法
if (mPerformClick == null) { //3
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) { //4
// 如果View设置OnClickListener,那么performClick就会调用View的onClick方法。
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();
}
mIgnoreNextUpEvent = false;
break;
case MotionEvent.ACTION_DOWN:
// Walk up the hierarchy to determine if we're inside a scrolling container.
boolean isInScrollingContainer = isInScrollingContainer();
// 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
//设置当前View的状态,以及更新View的drawable state,例如button按下、悬浮时显示不同的背景,即刷新背景
setPressed(true, x, y);
//开始长按监测,所以View的长按监测位于
checkForLongClick(0, x, y);
}
break;
case MotionEvent.ACTION_CANCEL:
if (clickable) {
setPressed(false);
}
removeTapCallback();
removeLongPressCallback();
mInContextButtonPress = false;
mHasPerformedLongPress = false;
mIgnoreNextUpEvent = false;
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
break;
case MotionEvent.ACTION_MOVE:
// Be lenient about moving outside of buttons
if (!pointInView(x, y, mTouchSlop)) {
// Outside button
// Remove any future long press/tap checks
removeTapCallback();
removeLongPressCallback();//当移出当前View时,会删除当前View的长按监测;
...
}
break;
}
return true;
}
return false;
}
private void checkForLongClick(int delayOffset, float x, float y) {
if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE || (mViewFlags & TOOLTIP) == TOOLTIP) {
mHasPerformedLongPress = false;
if (mPendingCheckForLongPress == null) {
mPendingCheckForLongPress = new CheckForLongPress();
}
mPendingCheckForLongPress.setAnchor(x, y);
mPendingCheckForLongPress.rememberWindowAttachCount();
mPendingCheckForLongPress.rememberPressedState();
//ViewConfiguration.getLongPressTimeout()时间为500ms,所以当用户按下去500ms之后就会回OnLongClickListener.onLongClick方法;
//如果500ms之内手指弹起,会发生ACTION_UP事件,此时会移出长按监测,这样就不会发生长按事件
postDelayed(mPendingCheckForLongPress,
ViewConfiguration.getLongPressTimeout() - delayOffset);
}
}
public boolean performClick() { //5
if (li != null && li.mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
// 如果设置了OnClickListener监听器,就回调onClick方法。
li.mOnClickListener.onClick(this);
result = true;
} else {
result = false;
}
return result;
}
源码2,3,5处说明当OnTouchEvent执行ACTION_UP事件时会触发View的onClick事件。
总结
整个View的事件分发流程:
View.dispatchEvent->View.setOnTouchListener->View.onTouchEvent
在dispatchTouchEvent中会进行OnTouchListener的判断,如果OnTouchListener不为null且返回true,则表示事件被消费,onTouchEvent不会被执行;否则执行onTouchEvent。