从百度上找了一张安卓事件分发U型图,下面从源码角度来分析这张图
当一个点击事件发生时,事件最先传到Activity的dispatchTouchEvent()
进行事件分发
//
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
/*
DOWN事件调用onUserInteraction(),该方法为空方法当此activity在栈顶时,触屏点击按home,
back,menu键等都会触发此方法,我们可以重写他
getWindow() 查看源码该方法获得window类对象,window是一个抽象类,其唯一实现子类是
phoneWindow,在Source InSight查看phoneWindow的superDispatchTouchEvent(ev)方法
*/
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
/*
可以看到调用了mDecor的superDispatchTouchEvent(event)方法,mDecor是一个DecorView对象,
查看DecorView源码
*/
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
/*
可以看到调用父类的dispatchTouchEvent(event),DecorView继承自FrameLayout,FrameLayout继承
自ViewGroup,如上图所示此时传递到了ViewGroup的dispatchTouchEvent()方法
*/
接下来分析ViewGroup的源码,ViewGroup的dispatchTouchEvent方法比较长,截取了下面一段
// Check for interception.
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
} else {
// There are no touch targets and this action is not an initial down
// so this view group continues to intercept touches.
intercepted = true;
}
/*
定义了一个 boolean 值变量 intercept 来表示是否要拦截事件。onInterceptTouchEvent(ev) 对
intercept 进行赋值。大多数情况下,onInterceptTouchEvent() 返回值为 false,但我们完全可以通过重
写 onInterceptTouchEvent(ev) 来改变它的返回值,
*/
继续向下阅读源码
/*
由于代码太长,我们节选一部分,并结合源码中注释去理解
*/
if (!canceled && !intercepted) {
.......
final int childrenCount = mChildrenCount;
if (newTouchTarget == null && childrenCount != 0) {
final float x = ev.getX(actionIndex);
final float y = ev.getY(actionIndex);
// Find a child that can receive the event.
// Scan children from front to back.
final ArrayList preorderedList = buildTouchDispatchChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);
.......
}
}
.......
return handled;
}
/*
if (!canceled && !intercepted) 忽略里面!canceled,实际上这里就是我们没有重写
onInterceptTouchEvent,让它返回true,就可以进到方法里,后面做了一个 For 循环,遍历 ViewGroup
下面的所有子 View,源码中有很多的if判断,实际就是一个一个判断点击位置是否是该子 View 的布局区
域。最后返回的handled实际就是 super.dispatchTouchEvent(event),即View的dispatchTouchEvent
*/
上边源码看到传递到了View的dispatchTouchEvent方法,我们继续看view的dispatchTouchEvent方法
public boolean dispatchTouchEvent(MotionEvent event) {
......
boolean result = false;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
/*
如果这几个条件有一个没有满足,那么就看下面第二个if语句。
*/
if (!result && onTouchEvent(event)) {
result = true;
}
return result;
.....
}
/*
节选了一部分代码
li != null:ListenerInfor里面包含了几个Listener,如TouchListener、TouchListener,FocusChangeListener,
LayoutChangeListeners,ScrollChangeListener等。我们可以认为为真
只有以下3个条件都为真,result = true,此时才能进入下一个循环 dispatchTouchEvent()才返回true;否则执行onTouchEvent()
1. mOnTouchListener != null
2. (mViewFlags & ENABLED_MASK) == ENABLED
3. mOnTouchListener.onTouch(this, event)
若在onTouch()返回true,就会让上述三个条件全部成立,从而使得View.dispatchTouchEvent()直接返回true,事件分发结束
若在onTouch()返回false,就会使得上述三个条件不全部成立,从而使得View.dispatchTouchEvent()
中跳出If,执行下一个if语句,那么此时关键在于onTouchEvent这个方法,如 果onTouEvent返回true,那
么就表示消耗了这个事件。可以看出onTouchListener的优先级比onTouchEvent优先级高,
*/
上边第一个条件li.mOnTouchListener != null,mOnTouchListener变量在View.setOnTouchListener()方法里赋值,
即只要我们给控件注册了Touch事件,mOnTouchListener就一定被赋值(不为空)
public void setOnTouchListener(OnTouchListener l) {
getListenerInfo().mOnTouchListener = l;
}
第二个条件 (mViewFlags & ENABLED_MASK) == ENABLED
该条件是判断当前点击的控件是否为 enable,但由于基本 View 都是 enable 的,所以这个条件基本都返回 true。
第三个条件mOnTouchListener.onTouch(this, event)
即我们调用 setOnTouchListener()
时必须覆盖的方法 onTouch()
的返回值。
//即 回调控件注册Touch事件时的onTouch();需手动复写设置,具体如下(以按钮Button为例)
button.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
return false;
}
});
// 若在onTouch()返回true,就会让上述三个条件全部成立,从而使得View.dispatchTouchEvent()直接返回true,事件分发结束
/*
若在onTouch()返回false,就会使得上述三个条件不全部成立,从而使得View.dispatchTouchEvent()
中跳出If,执行下一个if语句,那么此时关键在于onTouchEvent这个方法,如 果onTouEvent返回true,那么
就表示消耗了这个事件。可以看出onTouchListener的优先级比onTouchEvent优先级高,
*/
从上述的分析,onTouch()
方法优先级高于 onTouchEvent(event)
方法
继续看onTouchEvent源码
public boolean onTouchEvent(MotionEvent event) {
......
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
(viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {
switch (action) {
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 && !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.
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();
}
mIgnoreNextUpEvent = false;
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();
// 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, x, y);
}
break;
case MotionEvent.ACTION_CANCEL:
setPressed(false);
removeTapCallback();
removeLongPressCallback();
mInContextButtonPress = false;
mHasPerformedLongPress = false;
mIgnoreNextUpEvent = false;
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;
}
观察我们上边的代码,如果CLICKABLE或LONG_CLICKABLE或CONTEXT_CLICKABLE满足任何一个,那么就返回true,也就是dispatchTouchEvent返回true,消耗了这个事件。如果都不满足就返回false,也就是没有消耗这个事件。需要注意的是在switch语句中,if语句里面判断MotionEvent.ACTION_UP:
的时候,如果满足里面的情况会调用performClick()
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);
return result;
}
只要我们通过setOnClickListener()为控件View注册1个点击事件 那么就会给mOnClickListener变量赋值(即不为空) 则会往下回调onClick() & performClick()返回true,前边分析过onTouch优先级高于onToucheEvent,所以方法执行优先级为:
onTouch>onToucheEvent>onClick