很多人都是學(xué)了忘交掏,忘了學(xué),感覺(jué)永遠(yuǎn)也記不住刃鳄。View 事件分發(fā)到底應(yīng)該怎么學(xué)盅弛?
其實(shí)很簡(jiǎn)單:
1、敲代碼
2叔锐、學(xué)習(xí)原理
3挪鹏、畫(huà)流程圖
View 事件分發(fā) 其實(shí)就4?部分組成
1、 dispatchTouchEvent(MotionEvent ev)
2愉烙、 onInterceptTouchEvent(MotionEvent ev)
3叠纹、 onTouchEvent(MotionEvent ev)
4搭伤、 MotionEvent
那么我們就詳細(xì)的了解一下這四個(gè)的具體含義。
dispatchTouchEvent(MotionEvent ev)
從名字就可以看出這個(gè)方法主要用來(lái)事件的分發(fā)鳞芙,每個(gè) View 都有這個(gè)方法姚建,如果消息能夠傳遞到這個(gè) View,那么這個(gè)方法一定能執(zhí)行。返回 true 代表消耗這個(gè)事件,false 表示不消耗振乏。這個(gè)結(jié)果受兩個(gè)因素的影響。
1秉扑、當(dāng)前 View 的 onTouchEvent() 的返回值慧邮。
2、下級(jí) View dispatchTouchEvent() 方法的返回值
onInterceptTouchEvent(MotionEvent ev)
這個(gè)方法是在dispatchTouchEvent() 中調(diào)用舟陆,误澳,事件攔截,這個(gè)只有在 ViewGroup 才有方法秦躯,View 是沒(méi)有的忆谓。如果當(dāng)前 View 攔截了這個(gè)事件,那么在一個(gè)事件序列中這個(gè)方法不會(huì)被再次調(diào)用了踱承。直到這個(gè)事件序列結(jié)束倡缠。返回的結(jié)果表示是否消耗了這個(gè)事件。
onTouchEvent(MotionEvent ev)
這個(gè)方法是在dispatchTouchEvent() 中調(diào)用勾扭,用來(lái)處理點(diǎn)擊事件毡琉,返回結(jié)果表示是否消耗了這個(gè)事件铁瞒。如果不消耗妙色,返回 false 那么在同一系列事件中,將不會(huì)再給這個(gè)事件派發(fā)慧耍,也就是再次收到事件身辨。
在藝術(shù)探索一書(shū)中有這么一段偽代碼
public boolean dispatchTouchEvent(MotionEvent ev){
boolean consume = false;
if(onInterceptTouchEvent(ev)){
consume = onTouchEvent(ev);
}else{
consume = child.dispatchTouchEvent(ev);
}
return consume;
}
我們可以通過(guò)記憶這一段簡(jiǎn)單的偽代碼,理解整個(gè) View 的事件分發(fā)芍碧。
1煌珊、一個(gè)事件序列
2、事件分發(fā)順序
通過(guò)偽代碼和圖示我們大概了解了 View 的事件分發(fā)泌豆,但是更具體的還需要源碼去分析定庵。
我們便從 Activity 到 View 一起來(lái)走一遍,看看具體代碼是怎么實(shí)現(xiàn)的踪危,里面有什么細(xì)節(jié)給我們解惑的蔬浙。
1、Activity
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
我們結(jié)合上圖和源碼贞远,可以看到很簡(jiǎn)單的幾段代碼畴博,事件分發(fā)調(diào)用的是 Windowd 的superDispatchTouchEvent 分發(fā)。接著往下看蓝仲。
我們知道 Window 是個(gè)抽象類(lèi)俱病,所以用實(shí)現(xiàn)類(lèi) PhoneWindow官疲。
mWindow = new PhoneWindow(this, window, activityConfigCallback);
PhoneWindow 又托管給 DecorView ,就是我們說(shuō)的 RootView 或者 RootViewGroup亮隙。
mDecor = (DecorView) preservedWindow.getDecorView();
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
其實(shí)就是 ViewGroup 的dispatchTouchEvent 方法途凫。
public boolean dispatchTouchEvent(MotionEvent ev) {
······
//從這里開(kāi)始 默認(rèn) handled 為 false 表示不消耗
boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
// Handle an initial down.
//當(dāng)事件 是 按下的時(shí)候 也是這一事件系列的開(kāi)始
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Throw away all previous state when starting a new touch gesture.
// The framework may have dropped the up or cancel event for the previous gesture
// due to an app switch, ANR, or some other state change.
// mFirstTouchTarget = null;重置mFirstTouchTarget 和 FLAG_DISALLOW_INTERCEPT
cancelAndClearTouchTargets(ev);
resetTouchState();
}
// 表示是否攔截
final boolean intercepted;
//當(dāng)事件類(lèi)型為 ACTION_DOWN 或者 mFirstTouchTarget 不為空的時(shí)候
//什么是 mFirstTouchTarget ?整體分析可知 當(dāng)這個(gè)事件被子 view 消耗的時(shí)候咱揍,
//mFirstTouchTarget 會(huì)指向這個(gè) view颖榜,也就是第一個(gè)消耗這個(gè)事件的 view。
//當(dāng) ViewGroup 不攔截的時(shí)候且子 view消耗了這個(gè)事件 那么mFirstTouchTarget 煤裙!=null掩完。
//那么 當(dāng)事件類(lèi)型為 MOVE 或者 UP 的時(shí)候,如果 mFirstTouchTarget 硼砰!=null 那就說(shuō)明
//有 view 消耗這個(gè)事件了且蓬,該系列的事件都默認(rèn)交給它處理
//也就是事件的開(kāi)始 和 事件有消耗后 都會(huì)走這個(gè)方法。當(dāng)然也不是絕對(duì)的
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
//FLAG_DISALLOW_INTERCEPT這個(gè)參數(shù)是子 view 通過(guò)requestDisallowInterceptTouchEvent 方法题翰,改變這個(gè)值 恶阴,來(lái)控制 父 view 是否攔截
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
//當(dāng)需要攔截的時(shí)候 那就走自己的onInterceptTouchEvent 方法具體自己實(shí)現(xiàn) ,不需要那就intercepted 為 false
//需要注意的是 對(duì)于 DOWN 子 view 通過(guò) requestDisallowInterceptTouchEvent 方法是控制不了父 view 的攔截方法的豹障,因?yàn)楫?dāng)有 DOWN 事件的時(shí)候 就會(huì)觸發(fā) 之前的重置方法冯事。
//所以只能針對(duì) MOVE UP 等,可以讓 父 view 不在攔截事件血公,可以使父 view 開(kāi)始攔截事件
//正是因?yàn)?FLAG_DISALLOW_INTERCEPT 昵仅,如果我們父 view 開(kāi)始攔截,并消耗了這個(gè)事件累魔,那么 mFirstTouchTarget摔笤!=null 就不會(huì)成立,因?yàn)閂iewGroup 的子 view 成功消耗這個(gè)事件后 才會(huì)mFirstTouchTarget垦写!=null吕世。所以以后的事件也就不會(huì)再走 onInterceptTouchEvent,直接返回 intercepted = true 梯投。
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;
}
// If intercepted, start normal event dispatch. Also if there is already
// a view that is handling the gesture, do normal event dispatch.
if (intercepted || mFirstTouchTarget != null) {
ev.setTargetAccessibilityFocus(false);
}
// Check for cancelation.
final boolean canceled = resetCancelNextUpFlag(this)
|| actionMasked == MotionEvent.ACTION_CANCEL;
// Update list of touch targets for pointer down, if needed.
final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
TouchTarget newTouchTarget = null;
boolean alreadyDispatchedToNewTouchTarget = false;
//當(dāng)ViewGroup不攔截時(shí)命辖,這里對(duì) ViewGroup 的子 view 進(jìn)行處理
if (!canceled && !intercepted) {
// If the event is targeting accessiiblity focus we give it to the
// view that has accessibility focus and if it does not handle it
// we clear the flag and dispatch the event to all children as usual.
// We are looking up the accessibility focused host to avoid keeping
// state since these events are very rare.
View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
? findChildWithAccessibilityFocus() : null;
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
final int actionIndex = ev.getActionIndex(); // always 0 for down
final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
: TouchTarget.ALL_POINTER_IDS;
// Clean up earlier touch targets for this pointer id in case they
// have become out of sync.
removePointersFromTouchTargets(idBitsToAssign);
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<View> preorderedList = buildTouchDispatchChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
//遍歷子 view
for (int i = childrenCount - 1; i >= 0; i--) {
//這一堆的代碼有點(diǎn)復(fù)雜 不過(guò)就一個(gè)目的 當(dāng)前子 view 是都能接受點(diǎn)擊事件
//如何判斷呢? 1分蓖、是否在播放動(dòng)畫(huà) 2尔艇、點(diǎn)擊的坐標(biāo)是否在該子 view 的區(qū)域
final int childIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);
// If there is a view that has accessibility focus we want it
// to get the event first and if not handled we will perform a
// normal dispatch. We may do a double iteration but this is
// safer given the timeframe.
if (childWithAccessibilityFocus != null) {
if (childWithAccessibilityFocus != child) {
continue;
}
childWithAccessibilityFocus = null;
i = childrenCount - 1;
}
//這就是判斷是否可以點(diǎn)擊的方法之一 view 要顯示,是否在播放動(dòng)畫(huà)
/**
* Returns true if a child view can receive pointer events.
* @hide
* private static boolean canViewReceivePointerEvents(@NonNull View child) {
* return (child.mViewFlags & VISIBILITY_MASK) == VISIBLE
* || child.getAnimation() != null;
* }
*/
//判斷二 是否在子 view 的范圍
/**
* protected boolean isTransformedTouchPointInView(float x, float y, View child,
PointF outLocalPoint) {
final float[] point = getTempPoint();
point[0] = x;
point[1] = y;
transformPointToViewLocal(point, child);
final boolean isInView = child.pointInView(point[0], point[1]);
if (isInView && outLocalPoint != null) {
outLocalPoint.set(point[0], point[1]);
}
return isInView;
}
*
*/
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
//mFirstTouchTarget 是一個(gè)鏈表形式 遍歷這個(gè)鏈表 如果有這個(gè) view 那么就直接賦值 newTouchTarget
//注意 mFirstTouchTarget 是在View 的子 view中賦值的
// 說(shuō)明點(diǎn)擊確實(shí)在子 view 的區(qū)域 直接返回不用再繼續(xù)遍歷了
newTouchTarget = getTouchTarget(child);
if (newTouchTarget != null) {
// Child is already receiving touch within its bounds.
// Give it the new pointer in addition to the ones it is handling.
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}
resetCancelNextUpFlag(child);
//實(shí)際是 調(diào)用 子 view 的 dispatchTouchEvent 方法咆疗。依次遞歸下去
//如果 子 view dispatchTouchEvent 返回true 那么 newTouchTarget = addTouchTarget(child, idBitsToAssign);
// newTouchTarget 被賦值并跳出循環(huán) 其實(shí)是 mFirstTouchTarget鏈表添加數(shù)據(jù)
alreadyDispatchedToNewTouchTarget = true;
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// Child wants to receive touch within its bounds.
mLastTouchDownTime = ev.getDownTime();
if (preorderedList != null) {
// childIndex points into presorted list, find original index
for (int j = 0; j < childrenCount; j++) {
if (children[childIndex] == mChildren[j]) {
mLastTouchDownIndex = j;
break;
}
}
} else {
mLastTouchDownIndex = childIndex;
}
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
// The accessibility focus didn't handle the event, so clear
// the flag and do a normal dispatch to all children.
ev.setTargetAccessibilityFocus(false);
}
if (preorderedList != null) preorderedList.clear();
}
//當(dāng) newTouchTarget 還是null 那就 鏈表 最后一個(gè) 賦值給newTouchTarget
if (newTouchTarget == null && mFirstTouchTarget != null) {
// Did not find a child to receive the event.
// Assign the pointer to the least recently added target.
newTouchTarget = mFirstTouchTarget;
while (newTouchTarget.next != null) {
newTouchTarget = newTouchTarget.next;
}
newTouchTarget.pointerIdBits |= idBitsToAssign;
}
}
}
// Dispatch to touch targets.
if (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary view.
//當(dāng) mFirstTouchTarget 為空 其實(shí)也就是子 view 為空漓帚,調(diào)用 view 的 dispatchTouchEvent 方法
//就是 viewGroup 如果需要消耗事件 其實(shí)調(diào)用的是 view 的方法
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
// Dispatch to touch targets, excluding the new touch target if we already
// dispatched to it. Cancel touch targets if necessary.
TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
while (target != null) {
final TouchTarget next = target.next;
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
} else {
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
if (cancelChild) {
if (predecessor == null) {
mFirstTouchTarget = next;
} else {
predecessor.next = next;
}
target.recycle();
target = next;
continue;
}
}
predecessor = target;
target = next;
}
}
// Update list of touch targets for pointer up or cancel, if needed.
//更新重置 狀態(tài)
if (canceled
|| actionMasked == MotionEvent.ACTION_UP
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
resetTouchState();
} else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
final int actionIndex = ev.getActionIndex();
final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
removePointersFromTouchTargets(idBitsToRemove);
}
}
if (!handled && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
}
//最后返回 消耗的情況
return handled;
}
我們來(lái)看下 ViewGroup 的分發(fā)。
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
// Canceling motions is a special case. We don't need to perform any transformations
// or filtering. The important part is the action, not the contents.
final int oldAction = event.getAction();
if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
event.setAction(MotionEvent.ACTION_CANCEL);
//當(dāng)沒(méi)有子 view 那么它就相當(dāng)于 view 調(diào)用 view 的方法午磁。
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
//如果有 那么就 遞歸這個(gè)方法了
handled = child.dispatchTouchEvent(event);
}
event.setAction(oldAction);
return handled;
}
// Calculate the number of pointers to deliver.
final int oldPointerIdBits = event.getPointerIdBits();
final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;
// If for some reason we ended up in an inconsistent state where it looks like we
// might produce a motion event with no pointers in it, then drop the event.
if (newPointerIdBits == 0) {
return false;
}
// If the number of pointers is the same and we don't need to perform any fancy
// irreversible transformations, then we can reuse the motion event for this
// dispatch as long as we are careful to revert any changes we make.
// Otherwise we need to make a copy.
final MotionEvent transformedEvent;
if (newPointerIdBits == oldPointerIdBits) {
if (child == null || child.hasIdentityMatrix()) {
//當(dāng)沒(méi)有子 view 那么它就相當(dāng)于 view 調(diào)用 view 的方法尝抖。
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
event.offsetLocation(offsetX, offsetY);
handled = child.dispatchTouchEvent(event);
event.offsetLocation(-offsetX, -offsetY);
}
return handled;
}
transformedEvent = MotionEvent.obtain(event);
} else {
transformedEvent = event.split(newPointerIdBits);
}
// Perform any necessary transformations and dispatch.
if (child == null) {
//當(dāng)沒(méi)有子 view 那么它就相當(dāng)于 view 調(diào)用 view 的方法毡们。
handled = super.dispatchTouchEvent(transformedEvent);
} else {
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
transformedEvent.offsetLocation(offsetX, offsetY);
if (! child.hasIdentityMatrix()) {
transformedEvent.transform(child.getInverseMatrix());
}
handled = child.dispatchTouchEvent(transformedEvent);
}
// Done.
transformedEvent.recycle();
return handled;
}
其實(shí)我們發(fā)現(xiàn)這一切都和 mFirstTouchTarget 有關(guān)系,當(dāng) viewGroup 需要自己消耗事件的時(shí)候昧辽,那么 mFirstTouchTarget 肯定是 null衙熔,也就是子 view 不消耗,或者自己攔截搅荞,都會(huì)造成 mFirstTouchTarget ==null红氯,那么當(dāng)調(diào)用 dispatchTransformedTouchEvent 方法是,就會(huì)觸發(fā) view 的 onTouchEvent 方法咕痛,viewGroup 自己去處理事件痢甘。
首先我們看看 View 的 dispatchTouchEvent() 方法(只保留了我們需要的源碼)。
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);
}
//這里是開(kāi)始
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)) {
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;
//判斷是否有監(jiān)聽(tīng)事件 并且 mOnTouchListener不為空
//view 的 enabled 為true 并且onTouch方法也返回true
//那么直接返回 true
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
//當(dāng) onTouch 被調(diào)動(dòng) 且返回true 那么onTouchEvent不會(huì)調(diào)用了
if (!result && onTouchEvent(event)) {
result = true;
}
}
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;
}
我們會(huì)發(fā)現(xiàn) View 可以調(diào)用 onTouch() 且返回 true, onTouchEvent就不會(huì)再調(diào)用了茉贡,這就是一個(gè)優(yōu)先級(jí)的問(wèn)題了塞栅。
最后是 onTouchEvent 方法
public boolean onTouchEvent(MotionEvent event) {
final float x = event.getX();
final float y = event.getY();
final int viewFlags = mViewFlags;
final int action = event.getAction();
//是否可點(diǎn)擊
final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
//就算 不可用的情況下 也可能會(huì)消耗點(diǎn)擊事件 只要 clickable 為true
if ((viewFlags & ENABLED_MASK) == DISABLED) {
if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
// A disabled view that is clickable still consumes the touch
// events, it just doesn't respond to them.
return clickable;
}
//如果 view 有代理會(huì)做這個(gè)方法 具體不說(shuō)了
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
//只要控件的clickable 或者 long_clickable 或者 CONTEXT_CLICKABLE 有一個(gè)為 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
//這是 up 所以 長(zhǎng)按事件回調(diào)移除
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.
// 我們?cè)O(shè)置點(diǎn)擊事件 是在這兒 開(kāi)始的~~看好了 我們發(fā)現(xiàn)是通過(guò) post 把 run 里的調(diào)用 performClick() 方法。用 Runnable 腔丧,通過(guò) post 形式執(zhí)行 performClick放椰,而不是直接調(diào)用 performClick , 因?yàn)檫@可以讓這個(gè) view 更新的視覺(jué)狀態(tài)在這個(gè)方法執(zhí)行之前愉粤。
//這個(gè)比如當(dāng)這個(gè) view 繪制 顯示 完成之后 才可以 點(diǎn)擊 之類(lèi)的 等等
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:
if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN) {
mPrivateFlags3 |= PFLAG3_FINGER_DOWN;
}
mHasPerformedLongPress = false;
if (!clickable) {
checkForLongClick(0, x, y);
break;
}
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:
if (clickable) {
setPressed(false);
}
removeTapCallback();
removeLongPressCallback();
mInContextButtonPress = false;
mHasPerformedLongPress = false;
mIgnoreNextUpEvent = false;
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
break;
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;
}
//當(dāng) clickable 為 true 那么一定返回 true 不管執(zhí)行了 點(diǎn)擊事件啥 或者 其他 這個(gè)事件在這兒一定會(huì)消耗
//就是返回 true 所以當(dāng)設(shè)置 不可點(diǎn)擊的時(shí)候且clickable 為false 才會(huì)不消耗這個(gè)事件
return true;
}
return false;
}
超級(jí)會(huì)員:mOnTouchListener
會(huì)員:onTouchEvent
普通用戶(hù):setOnClickListener
最后根據(jù)上面的源碼分析后砾医,我們整理一些結(jié)論,結(jié)合結(jié)論和源碼分析衣厘,我們更好的理解 View的事件分發(fā)如蚜。(copy from 《藝術(shù)探索》 + 自己的理解)
1、同一個(gè)事件序列是從手指觸摸屏幕的那一刻開(kāi)始头滔,直到手指離開(kāi)屏幕的那一刻結(jié)束怖亭,在這個(gè)過(guò)程產(chǎn)生的一系列事件涎显,是從 down 開(kāi)始 經(jīng)過(guò) n 個(gè) move 最后 up 結(jié)束坤检。
2、正常情況下期吓,一個(gè)事件只能被一個(gè) View 攔截且消耗早歇,原因參考 3。一旦一個(gè) View 攔截了某事件讨勤,那么這個(gè)事件序列內(nèi)的所有事物都會(huì)直接交給它處理箭跳,因此同一個(gè)事件序列中的事件不能由兩個(gè) view 同時(shí)處理,注意是同時(shí)潭千,但是通過(guò)特殊手段可是谱姓,比如將一個(gè)View 的事件強(qiáng)行傳遞給其他view,這是無(wú)賴(lài)操作刨晴。
3屉来、某個(gè) View 一旦決定攔截路翻,那么這個(gè)事件序列只能由他來(lái)出來(lái),如果事件能傳遞給他茄靠,而且他的 onInterceptTouchEvent 不會(huì)再繼續(xù)調(diào)用茂契,因?yàn)?View 攔截那么mFirstTouchTarget 就不會(huì)在 子 View 中賦值,mFirstTouchTarget ==null 直接返回 true 慨绳,最后走 View 的 ouTouchEvent 方法掉冶。
4、某個(gè) View 一旦開(kāi)始處理事件脐雪,如果它不消耗 ACTION_DOWN 事件( onTouchEvent 返回 false)厌小,那么同一序列的而其他事件都不會(huì)交給他處理,而是將事件重新交給他的父元素處理战秋,即 父 View 的 onTouchEvent 會(huì)調(diào)用 召锈。源碼上其實(shí)就是 比如子 View onTouchEvent 返回 false,因?yàn)?dispatchTouchEvent 是 遞歸的获询,所以返回 子 view 的dispatchTouchEvent 返回 false涨岁,最后 mFirstTouchTarget 還是null ,所以 父 view(ViewGroup) 調(diào)用 View 的 dispatchTouchEvent 進(jìn)行處理吉嚣,最后父 View調(diào)用onTouchEvent 方法梢薪,來(lái)處理這個(gè)事件。
5尝哆、如果 View 不消耗 ACTION_DOWN 以外的其他事件秉撇,那么這個(gè)點(diǎn)擊事件就會(huì)消失,此時(shí)的父元素的 onTouchEvent 不會(huì)調(diào)用秋泄,而且當(dāng)前 View 可以持續(xù)收到后續(xù)的事件琐馆,最終這些消失的事件會(huì)交給 Activity 處理。
6恒序、ViewGroup 默認(rèn)不攔截任何事件瘦麸,即 onInterceptTouchEvent 返回 false。
7歧胁、View 沒(méi)有 onInterceptTouchEvent滋饲。事件傳遞給它,ouTouchEvenr 就會(huì)調(diào)用喊巍。
8屠缭、View 的 ouTouchEvent 會(huì)默認(rèn)消耗事件 返回true。除非他是不可點(diǎn)擊 clickable 和longclickable 同時(shí)是 false崭参。longclickable默認(rèn)為false呵曹,button 默認(rèn) clickable 是true,textview 默認(rèn) false。
9奄喂、View 的enable 屬性不影響 ouTouchEvent 的返回值之剧,只要longclickable 或 clickable 有一個(gè)是 true ,那么ouTouchEvent 就返回 true砍聊。
10背稼、onClick 會(huì)發(fā)生的前提是 view 可點(diǎn)擊。并過(guò)去收到了down 和up玻蝌。
11蟹肘、事件傳遞是從外向內(nèi)的,事件總是先傳遞給父view俯树,然后再有父view分發(fā)給子view帘腹,通過(guò) requestDisallowInterceptTouchEvent 方法,子類(lèi)可以干預(yù)父 View 的事件分發(fā)過(guò)程许饿。ACTION_DOWN 除外阳欲。
12、mOnTouchListener > onTouchEvent > setOnClickListener
參考 《Android開(kāi)發(fā)藝術(shù)探索》