首先我们确定下一般View的几种事件触发方法:
dispatchTouchEvent
onTouchListener
onTouchEvent
onClickListener
首先我们来一个示例,重写了Button,代码如下:
public class MyButton extends Button
{
private static final String TAG = MyButton.class.getSimpleName();
public MyButton(Context context, AttributeSet attrs)
{
super(context, attrs);
}
@Override
public boolean onTouchEvent(MotionEvent event)
{
int action = event.getAction();
switch (action)
{
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)
{
int action = event.getAction();
switch (action)
{
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);
}
}
MainActivity的代码如下:
MyButton btn = (MyButton)findViewById(R.id.button_test);
btn.setOnTouchListener(new View.OnTouchListener()
{
@Override
public boolean onTouch(View v, MotionEvent event)
{
int action = event.getAction();
switch (action)
{
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;
}
});
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.e(TAG, "OnClickListener ACTION_CLICK");
}
});
重写了Button的onTouchEvent、dispatchTouchEvent,在MainActivity中设置了OnTouchListener、OnClickListener。
日志显示的顺序是dispatchTouchEvent、OnTouchListener、onTouchEvent、最后是OnClickListener。
以下是view的dispatchTouchEvent方法代码:
if (event.isTargetAccessibilityFocus()) {....}
if (mInputEventConsistencyVerifier != null) {.....}
final int actionMasked = event.getActionMasked();
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Defensive cleanup for new gesture
stopNestedScroll();
}
//判断是否被其它窗口遮挡
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;
}
}
if (!result && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
}
if (actionMasked == MotionEvent.ACTION_UP ||
actionMasked == MotionEvent.ACTION_CANCEL ||
(actionMasked == MotionEvent.ACTION_DOWN && !result)) {
stopNestedScroll();
}
actionMasked如果是按下事件则暂停View的滚动,如果当前view在滚动则停止滚动,接着判断当前的view是否需要消费事件,这里消费场景特定为是否处于窗口最前端,确认之后有三个过程:
1、是否正在滚动,若正在滚动则跳过事件处理
2、是否有Touch事件的监听,如果有则传入监听的Touch事件
3、传递给自己的onTouchEvent
这三个过程中如果有消费掉事件则停止事件的继续分发。其中onFilterTouchEventForSecurity是用来判断当前是否在窗口最前端,代码如下:
if ((mViewFlags & FILTER_TOUCHES_WHEN_OBSCURED) != 0
&& (event.getFlags() & MotionEvent.FLAG_WINDOW_IS_OBSCURED) != 0) {
// 窗口被遮挡,消费该事件
return false;
}
return true;
下面我们看下onTouchEvent是如何处理按键事件的:
// view被设置为disabled时候,touch事件照样被执行,但是不响应
if ((viewFlags & ENABLED_MASK) == DISABLED) {
if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
return (((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE);
}
//触摸事件代理
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
ACTION_UP操作:
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) {
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;
首先判断是否有PFLAG_PRESSED或者是PFLAG_PREPRESSED的状态;接着获取焦点操作。
如果是PFLAG_PREPRESSED的状态则刷新标志位。
mHasPerformedLongPress是表示是否触发了长按操作
mIgnoreNextUpEvent是否忽略UP事件
如果两个都为false,则执行删除检测长按的回调,如果当前焦点在自身上则,执行点击事件的触发,其中会通知OnClickListener。
后续的mUnsetPressedState是为了重置view的状态,比如按下状态的改变,延迟时间为64ms。最后删除TapCallback,下文会介绍,这是一个分辨滚动的回调操作。
ACTION_DOWN操作
mHasPerformedLongPress = false;
//鼠标右键则不消费此事件,直接结束
if (performButtonActionOnTouchDown(event)) {
break;
}
// 判断是否在滚动的容器中
boolean isInScrollingContainer = isInScrollingContainer();
// 如果在滚动容器中,则延迟按下的反馈,因为可能是滚动事件
if (isInScrollingContainer) {
mPrivateFlags |= PFLAG_PREPRESSED;
if (mPendingCheckForTap == null) {
mPendingCheckForTap = new CheckForTap();
}
mPendingCheckForTap.x = event.getX();
mPendingCheckForTap.y = event.getY();
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
} else {
// 没有处于滚动容器中,则立刻反馈按下
setPressed(true, x, y);
checkForLongClick(0, x, y);
}
首先mHasPerformedLongPress置为false,因为还没有检测到长按事件,接下来是判断是否在可滚动的容器中,如果是,则首先会把标志位设为PFLAG_PREPRESSED,接下来会有一个延迟执行的操作,时间为ViewConfiguration.getTapTimeout(),100ms。CheckForTap的代码为:
private final class CheckForTap implements Runnable {
public float x;
public float y;
@Override
public void run() {
mPrivateFlags &= ~PFLAG_PREPRESSED;
setPressed(true, x, y);
checkForLongClick(ViewConfiguration.getTapTimeout(), x, y);
}
}
也就是说如果100ms之后还是按下的状态则取消掉PFLAG_PREPRESSED状态,并且反馈按下事件并且标志位刷新为PFLAG_PRESSED,如果有长按事件也一并检测。
ACTION_MOVE操作
case MotionEvent.ACTION_MOVE:
drawableHotspotChanged(x, y);
if (!pointInView(x, y, mTouchSlop)) {
removeTapCallback();
if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
removeLongPressCallback();
setPressed(false);
}
}
首先更新当前触摸的(x,y)坐标,如果未出界的话则执行removeTapCallback,这是干啥的呢,之前在DOWN事件中有一个延迟处理,如果100ms如果出界的话,则删除回调。若有长按的回调也一并删除,并重置按键状态为false。
以上是view的事件分发,但是其实在使用的过程中最底层view本身的事件判断是最后执行的,它的事件来源都是上层下发的,具体的流程如下图:
这里ViewGroup重写了View的dispatchTouchEvent 事件,并分发给子view,具体如何分发可以自己看源码。