这篇文章我们来探究下Android中关于事件分发机制的一些细节和流程,由于这部分源码比较繁杂,拆开来讲,本文只探究View的事件分发流程,ViewGroup留到之后再说,在分析完这两者的事件分发机制之后我们来对Android的时间分发机制进行总结。那么本文就从View的子类Button来着手分析事件分发的流程,之后我们再从源码角度分析具体实现过程。
这里用一个很简单的小例子来演示View中dispatchTouchEvent、onTouchEvent、TouchListener的执行顺序,继承自Button的TestButton代码很简单,几个log:
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.e(TAG, "onTouchEvent ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
Log.e(TAG, "onTouchEvent ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Log.e(TAG, "onTouchEvent ACTION_UP");
break;
default:
break;
}
return super.onTouchEvent(event);
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.e(TAG, "dispatchTouchEvent ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
Log.e(TAG, "dispatchTouchEvent ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Log.e(TAG, "dispatchTouchEvent ACTION_UP");
break;
default:
break;
}
return super.dispatchTouchEvent(event);
}
Activity中设置onTouchListener,也是几个log:
testView.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.e(TAG, "onTouch ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
Log.e(TAG, "onTouch ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Log.e(TAG, "onTouch ACTION_UP");
break;
default:
break;
}
return false;
}
});
接下来是执行流程,第一次点击按钮:
第二次点击后鼠标移动一下松开:
可以看到一次事件分发从ACTION_DOWN开始,到ACTION_UP结束,且其传递顺序是从dispatchTouchEvent –> onTouch –> onTouchEvent。下面我们来从源码中对这几个方法进行查看。
我们先从View的dispatchTouchEvent开始吧:
public boolean dispatchTouchEvent(MotionEvent event) {
...
boolean result = false;
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)) {
result = true;
}
if (!result && onTouchEvent(event)) {
result = true;
}
}
...
return result;
}
我这里略去了部分代码,来看核心代码,可以看到第10行开始这里开始进行了判断,若同时附和这几个条件,则返回true,注意这里调用了OnTouchListener的onTouch方法,也就是说如果我们调用了View的setOnTouchListener方法那么在dispatchTouchEvent方法执行过程中,会调用OnTouchListener的onTouch方法,若onTouch方法返回true,则设置result为true,onTouchEvent不再执行,若onTouch方法返回为false,则第十行if语句不成立,result为改变,执行下一个判断语句,同时会执行onTouchEvent,若返回true则result为true,反之亦然。
我们继续来看一看onTouchEvent中的代码:
public boolean onTouchEvent(MotionEvent event) {
//View状态为Disable并且可点击,返回true
if ((viewFlags & ENABLED_MASK) == DISABLED) {
if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
return clickable;
}
//若TouchDelegate(触摸代理类)不为空,则调用其onTouchEvent方法并返回true
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
//若可点击或者可长按以及长按出现ToolTip
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
case MotionEvent.ACTION_UP:
...
break;
case MotionEvent.ACTION_DOWN:
...
break;
case MotionEvent.ACTION_CANCEL:
...
break;
case MotionEvent.ACTION_MOVE:
...
break;
}
return true;
}
return false;
}
这段代码极长,有兴趣的朋友可以通读一下源码,我这里略去了一部分,前两个判断已经在注释里写清楚了,不多赘述,我们重要看下switch语句中的内容,接下来一个一个分析:
ACTION_DOWN是整个Touch流程的起点,代表触摸点按下操作。我们来看看onTouchEvent中的判断是怎样的:
case MotionEvent.ACTION_DOWN:
mHasPerformedLongPress = false;
//判断是否为鼠标右键或者手写笔第一个按钮,若是,返回true后续代码不执行
if (performButtonActionOnTouchDown(event)) {
break;
}
//当前视图是否可滚动(例如:当前是ScrollView视图,返回true)
boolean isInScrollingContainer = isInScrollingContainer();
if (isInScrollingContainer) {
// 滚动视图内,先不设置为按下状态,因为用户之后可能是滚动操作
// 不是此次分析的重点,感兴趣可以自己了解下
mPrivateFlags |= PREPRESSED;
if (mPendingCheckForTap == null) {
mPendingCheckForTap = new CheckForTap();
}
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
} else {
// 不在滚动视图内,立即反馈为按下状态
mPrivateFlags |= PRESSED;
// 刷新为按下状态
refreshDrawableState();
//通过Handler发送一个延迟消息来判断是否是长按,500ms
checkForLongClick(0);
}
break;
各个步骤的注释已经写清楚了,下面分解一下各个方法:
首先是performButtonActionOnTouchDown:
protected boolean performButtonActionOnTouchDown(MotionEvent event) {
// 如果是鼠标右键,手写笔第一个按钮,看BUTTON_SECONDARY注释
if ((event.getButtonState() & MotionEvent.BUTTON_SECONDARY) != 0) {
if (showContextMenu(event.getX(), event.getY(), event.getMetaState())) {
return true;
}
}
return false;
}
很简单的判断,如果成功就返回true,不成功就false。
接下来是isInScrollingContainer:
public boolean isInScrollingContainer() {
ViewParent p = getParent();
while (p != null && p instanceof ViewGroup) {
if (((ViewGroup) p).shouldDelayChildPressedState()) {
return true;
}
p = p.getParent();
}
return false;
}
这里获取到了当前View的父控件,而后一层一层向上判断是否处于滚动容器中(shouldDelayChildPressedState返回true),如果是,则返回true
接下来是checkForLongClick:
private void checkForLongClick(int delayOffset) {
// 当前视图可以执行长按操作
if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) {
mHasPerformedLongPress = false;
if (mPendingCheckForLongPress == null) {
mPendingCheckForLongPress = new CheckForLongPress();
}
mPendingCheckForLongPress.rememberWindowAttachCount();
// 延迟一段时间(500ms)把runnable添加到消息队列
postDelayed(mPendingCheckForLongPress,
ViewConfiguration.getLongPressTimeout() - delayOffset);
}
}
ACTION_DOWN中的逻辑大概就是这样
ACTION_MOVE代表触摸点发生滑动
case MotionEvent.ACTION_MOVE:
//这个方法暂时不用关注
if (clickable) {
drawableHotspotChanged(x, y);
}
// Be lenient about moving outside of buttons
if (!pointInView(x, y, mTouchSlop)) {
// Outside button
// Remove any future long press/tap checks
removeTapCallback();
removeLongPressCallback();
if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
}
break;
我们先来看看pointInView方法里做了什么:
public boolean pointInView(float localX, float localY, float slop) {
return localX >= -slop && localY >= -slop && localX < ((mRight - mLeft) + slop) &&
localY < ((mBottom - mTop) + slop);
}
这个方法是判断是否划出控件可视区域,为了保证触摸点发生及轻微变化就导致ACTION_MOVE被执行,这里加入了一个slop的边界值,即在视图上下左右扩大slop
接下来是两个remove方法,这里我们放在一起讲:
private void removeTapCallback() {
if (mPendingCheckForTap != null) {
mPrivateFlags &= ~PFLAG_PREPRESSED;
removeCallbacks(mPendingCheckForTap);
}
}
private void removeLongPressCallback() {
if (mPendingCheckForLongPress != null) {
removeCallbacks(mPendingCheckForLongPress);
}
}
这两个方法主要是删除触摸和长按回调(还记得之前按下后发送的长按延时消息吗)
ACTION_CANCLE代表取消触摸操作,触摸流程结束
if (clickable) {
setPressed(false);
}
removeTapCallback();
removeLongPressCallback();
mInContextButtonPress = false;
mHasPerformedLongPress = false;
mIgnoreNextUpEvent = false;
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
这里代码很简单,清除View状态
ACTION_UP代表触摸点抬起操作,同样是一个触摸流程的结束
case MotionEvent.ACTION_UP:
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
if ((viewFlags & TOOLTIP) == TOOLTIP) {
handleTooltipUp();//popWindow之类相关的,不必关心
}
//清除触摸状态以及触摸回调和长按的回调
if (!clickable) {
removeTapCallback();
removeLongPressCallback();
mInContextButtonPress = false;
mHasPerformedLongPress = false;
mIgnoreNextUpEvent = false;
break;
}
boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
// 当前视图处于预按下或者按下状态,如果失去焦点,获取焦点状态
boolean focusTaken = false;
if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
focusTaken = requestFocus();
}
if (prepressed) {
//重设按下状态
setPressed(true, x, y);
}
//长按未触发
if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
//移除长按回调
removeLongPressCallback();
//按下状态执行点击事件
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)) {
//执行失败的话,保证视图不会永远处于按下状态
//直接执行一次
mUnsetPressedState.run();
}
// 清除轻触回调
removeTapCallback();
}
mIgnoreNextUpEvent = false;
break;
这短代码是触摸流程里最重要的一部分。代码中已经注释清除,不必全部理解,理解流程即可。
到此,我们整个View的触摸流程就结束了,光看这部分还是很绕的,下一篇文章我们会详细讲讲ViewGroup中事件分发流程,并且和本篇做对照,可能会理解的更透彻。
enjoy~
我的个人博客