為了以后能回顧時(shí)更方便斥废,在整個(gè)分析完以后在最上面粗?jǐn)M一個(gè)分析流程。
-
繼承Button類蚜退,重寫dispatchTouchEvent闰靴,onTouchEvent,onTouch三個(gè)方法
- 添加打印日志钻注,獲取直觀的方法調(diào)用順序
-
根據(jù)日志順序逐個(gè)解析方法蚂且,分析從進(jìn)入dispatchTouchEvent開(kāi)始一直到onTouchEvent中的觸發(fā)ACTION_UP的整個(gè)流程
- 最終邏輯都在onTouchEvent中
- ACTION_DOWN 按下時(shí)對(duì)狀態(tài)的保存,以及開(kāi)啟事件處理線程
- ACTION_MOVE 移動(dòng)狀態(tài)時(shí)對(duì)事件中斷判斷幅恋,以及取消事件處理線程
- ACTION_UP 抬起時(shí)杏死,對(duì)于onLongClick 、onClick回掉判斷捆交。
- 最終邏輯都在onTouchEvent中
-
收獲:
- onTouch 和onTouchEvent的關(guān)系
- onLongClick如何觸發(fā)
- onClick 如何觸發(fā)
- onLongClick 和onClick的關(guān)系
- 如何處理View的點(diǎn)擊
- 如何攔截事件自定義處理
一 淑翼、繼承Button 重寫事件處理相關(guān)的方法
- dispatchTouchEvent
- onTouchEvent方法
- 為View添加onTouch事件
按下Button 稍做滑動(dòng),觸發(fā)ACTION_DOWN零渐、ATION_MOVE窒舟、ACTION_UP三個(gè)事件
日志:
DOWN 事件
D/TButton: dispatchTouchEvent = ACTON_DOWN
D/MainActivity: onTouch = ACTON_DOWN
D/TButton: onTouchEvent = ACTON_DOWN
MOVE 事件
D/TButton: dispatchTouchEvent = ACTION_MOVE
D/MainActivity: onTouch = ACTION_MOVE
D/TButton: onTouchEvent = ACTION_MOVE
UP 事件
D/TButton: dispatchTouchEvent = ACTION_UP
D/MainActivity: onTouch = ACTION_UP
D/TButton: onTouchEvent = ACTION_UP
日志結(jié)論:三個(gè)方法的調(diào)用順序?yàn)?/strong>
1. dipatchTouchEvent
↓
2. onTouch
↓
3. onTouchEvent
二、 按順序進(jìn)源碼分析
- dipatchTouchEvent 方法
//先看方法注解诵盼。任何第一次見(jiàn)到的方法都應(yīng)該先看注解
/**
* Pass the touch screen motion event down to the target view, or this
* view if it is the target.
//通過(guò)觸摸屏幕移動(dòng)到目標(biāo)View,或者當(dāng)前View就是目標(biāo)View
//目標(biāo)View:屏幕上顯示的View都算
* @param event The motion event to be dispatched.
* @return True if the event was handled by the view, false otherwise.
//如果當(dāng)前View處理了此事件返回true,否則返回false
*/
public boolean dispatchTouchEvent(MotionEvent event) {
// If the event should be handled by accessibility focus first.
//如果此event作為第一個(gè)可訪問(wèn)的焦點(diǎn)被處理
if (event.isTargetAccessibilityFocus()) {
// We don't have focus or no virtual descendant has it, do not handle the event.
//我們沒(méi)有焦點(diǎn)或者沒(méi)有虛擬子類持有焦點(diǎn)惠豺,則不處理此事件
if (!isAccessibilityFocusedViewOrHost()) {
return false;
//這里直接返回false,不處理此事件银还。這里一直出現(xiàn)一個(gè)詞 focus.
//也就是說(shuō),target View 獲取不到焦點(diǎn)(我們將focusable = false) 將直接跳過(guò)此次事件處理洁墙,他還是能獲取到觸摸事件蛹疯,只是跳過(guò)處理
}
// We have focus and got the event, then use normal event dispatch.
//有焦點(diǎn)并且獲取了此事件,則使用默認(rèn)事件調(diào)度热监。呃-就是有焦點(diǎn)并且接收到了事件-就往下處理
event.setTargetAccessibilityFocus(false);
}
//先設(shè)置一個(gè)返回捺弦,默認(rèn)= false 不消費(fèi)。
boolean result = false;
//判斷是否是鍵盤輸入事件孝扛,如果先傳遞給輸入事件的onTouchEvent,并且還會(huì)繼續(xù)執(zhí)行后面的代碼
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(event, 0);
}
//獲取事件膜-這里不是太明白
final int actionMasked = event.getActionMasked();
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Defensive cleanup for new gesture
//防止新手勢(shì)被清楚列吼,停止嵌套滾動(dòng)
stopNestedScroll();
}
//這里過(guò)濾判斷里面 是判斷窗口是否被遮擋,如果被遮擋則終止處理返回false
if (onFilterTouchEventForSecurity(event)) {
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;
//下面的邏輯就是判斷是否消費(fèi)此事件
//1. 是否添加了onTouchlistener,2.是否為enabled狀態(tài)苦始,onTouch中返回true消費(fèi)了此次事件
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
//上面沒(méi)有沒(méi)有消費(fèi)寞钥,在onTouchEvent中返回為true進(jìn)行消費(fèi)。
if (!result && onTouchEvent(event)) {
result = true;
}
}
if (!result && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
}
//判斷手勢(shì)結(jié)束滾動(dòng)
// Clean up after nested scrolls if this is the end of
// also cancel it if we tried an ACTION_DOWN but wea gesture; 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;
}
** dispatchTouchEvent方法處理概括**
1 . 首要條件:目標(biāo)View是否有焦點(diǎn)陌选,無(wú)焦點(diǎn)返回false
2 . 是否添加了onTouchListener理郑,是否為可用狀態(tài),是否在onTouch中處理咨油。都滿足 返回true 表示已消費(fèi)
3 . 在onTouch沒(méi)有消費(fèi)(result= false)您炉,且選擇在onTouchEvent中進(jìn)行處理,則返回true 表示已消費(fèi)
這就解釋了日志中的處理順序:dispatchTouchEvent → onTouch → onTouchEvent役电。
所以對(duì)View設(shè)置了onTouchListener那么View自己的OnTouchEvent就不會(huì)執(zhí)行了
-
onTouchEvent 方法
源代碼中順序不方便閱讀赚爵,下面我們給ACTION換一下位置,從上到下按照觸發(fā)順序來(lái)分析
ACTION_DOWN → ACTION_MOVE → ACTION_UP → ACTION_CANCEL
先對(duì)方法內(nèi)的代碼進(jìn)行簡(jiǎn)單的閱讀并添加注解宴霸,按照事件觸發(fā)的順序囱晴,逐個(gè)解析補(bǔ)充一下,PFLAG_PREPRESSED瓢谢、PFLAG_PRESSED畸写。
* PFLAG_PREPRESSED:處理長(zhǎng)按事件的標(biāo)識(shí)。在ACTION_DOWN觸發(fā)后氓扛,開(kāi)啟延時(shí)線程處理長(zhǎng)按事件 但是延時(shí)間還未結(jié)束的狀態(tài)
* PFLAG_PRESSED :view是否被按下枯芬,只在setPressed()方法內(nèi)設(shè)置,判斷view是否被按下
在onTouchEvent 這兩個(gè)標(biāo)記被大量用于判斷采郎,理解了這兩個(gè)標(biāo)記的意義對(duì)理解整個(gè)處理邏輯會(huì)極大幫助千所。一開(kāi)始光看注釋我也是暈的,后來(lái)讀到后面結(jié)合上下文明白蒜埋。
/**
* Implement this method to handle touch screen motion events.
實(shí)現(xiàn)此方法來(lái)處理屏幕觸摸事件
* <p>
* If this method is used to detect click actions, it is recommended that
the actions be performed by implementing and calling
如果這個(gè)方法是用來(lái)監(jiān)測(cè)點(diǎn)擊動(dòng)作淫痰,建議通過(guò)調(diào)用實(shí)現(xiàn)此方法來(lái)處理action
* @return True if the event was handled, false otherwise.
//事件被處理返回 true 否則返回false
*/
public boolean onTouchEvent(MotionEvent event) {
//獲取事件觸發(fā)點(diǎn)的 橫軸坐標(biāo)
final float x = event.getX();
//獲取事件觸發(fā)點(diǎn)的 縱坐標(biāo)
final float y = event.getY();
//view狀態(tài)標(biāo)記
final int viewFlags = mViewFlags;
//獲取 事件類型
final int action = event.getAction();
if ((viewFlags & ENABLED_MASK) == DISABLED) {
if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
// A disabled view that is clickable still consumes the touch
//events, it just doesn't respond to them.
return (((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE);
}
//上面判斷viewFlags 是否被已禁用,如果被禁用并且有可點(diǎn)擊狀態(tài)則消費(fèi)此次事件整份,但不做處理
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
//是否由代理執(zhí)行
//進(jìn)入到事件消費(fèi)的邏輯待错,進(jìn)入到這個(gè)if判斷里面籽孙,最終都會(huì)返回true 表示已消費(fèi)此次傳遞的事件
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
(viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {
//判斷是否可點(diǎn),是否可長(zhǎng)按火俄,上下文是否可點(diǎn)
switch (action) {
case MotionEvent.ACTION_DOWN:
//將是否要執(zhí)行長(zhǎng)按 標(biāo)記 設(shè)為 false
mHasPerformedLongPress = false;
//直接執(zhí)行的ButtonAction處理 消費(fèi)了此次事件-暫時(shí)還不太明白
if (performButtonActionOnTouchDown(event)) {
break;
}
//確定是否在滾動(dòng)容器內(nèi)
// Walk up the hierarchy to determine if we're inside a scrolling container.
boolean isInScrollingContainer = isInScrollingContainer();
//view 在滾動(dòng)容器內(nèi)犯建,延遲短期按下的反饋防止這是一個(gè)滾動(dòng)
// For views inside a scrolling container, delay the pressed feedback for
a short period in case this is a scroll.
if (isInScrollingContainer) {
//mPrivateFlags 設(shè)置為 PFLAG_PREPRESSED(按下?tīng)顟B(tài)保存)
mPrivateFlags |= PFLAG_PREPRESSED;
//創(chuàng)建一個(gè)檢查器
if (mPendingCheckForTap == null) {
mPendingCheckForTap = new CheckForTap();
}
mPendingCheckForTap.x = event.getX();
mPendingCheckForTap.y = event.getY();
//發(fā)送一個(gè)延時(shí)為TapTimeout = 100 ms的消息,最終會(huì)進(jìn)入 checkForLongClick()中檢查執(zhí)行長(zhǎng)按
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
} else {
//不再滾動(dòng)容器內(nèi)瓜客,立刻反饋
// Not inside a scrolling container, so show the feedback right away
setPressed(true, x, y);
//檢查執(zhí)行長(zhǎng)按适瓦,延時(shí)事件為0
checkForLongClick(0);
}
break;
case MotionEvent.ACTION_MOVE:
//記錄變化的觸摸點(diǎn)坐標(biāo)
drawableHotspotChanged(x, y);
//如果移動(dòng)到了buttons以外的區(qū)域
// Be lenient about moving outside of buttons
if (!pointInView(x, y, mTouchSlop)) {
//移除回掉-里面移除的是CheckForTap,在滾動(dòng)容器內(nèi)的延時(shí)線程
// Outside button
removeTapCallback();
//判斷mPrivateFlags 是否被設(shè)置了PFLAG_PRESSED標(biāo)記
if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
//移除即將執(zhí)行的長(zhǎng)按回掉
// Remove any future long press/tap checks
removeLongPressCallback();
setPressed(false);
}
}
break;
case MotionEvent.ACTION_UP:
//是否在執(zhí)行長(zhǎng)按點(diǎn)擊事件
boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
//已被按下 或者觸發(fā)長(zhǎng)按事件
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) {
//為了確保用戶能看見(jiàn)按下的狀態(tài)谱仪,我們?cè)僭O(shè)置一次按下的狀態(tài)
// 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);
}
//不是長(zhǎng)按事件玻熙,也沒(méi)有忽略后續(xù)的upEvent
if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
//移除長(zhǎng)按處理回掉 既移除CheckForLongPress線程
// This is a tap, so remove the longpress check
removeLongPressCallback();
//只執(zhí)行一個(gè)點(diǎn)擊動(dòng)作
// Only perform take click actions if we were in the pressed state
if (!focusTaken) {
//使用Runnable 不如直接調(diào)用performClick ,這可以在點(diǎn)擊動(dòng)作開(kāi)始前就更新視圖
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();
}
//如果添加PerformClick到線程隊(duì)列,此線程中會(huì)調(diào)用 performClick()
//如果失敗則立即直接調(diào)用performClick()
if (!post(mPerformClick)) {
performClick();
}
//這一做地目的一時(shí)不太明白疯攒,希望有同學(xué)為我解惑呀
}
}
if (mUnsetPressedState == null) {
mUnsetPressedState = new UnsetPressedState();
}
//如果需要處理長(zhǎng)按事件揭芍,啟動(dòng)延時(shí)64ms的線程,同上添加到線程隊(duì)列失敗則立即調(diào)用run方法確保達(dá)到目的
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_CANCEL:
setPressed(false);
removeTapCallback();
removeLongPressCallback();
mInContextButtonPress = false;
mHasPerformedLongPress = false;
mIgnoreNextUpEvent = false;
break;
}
return true;
}
return false;
}
1.ACTION_DOWN
核心代碼:
···
if (isInScrollingContainer) {
if (mPendingCheckForTap == null) {
mPendingCheckForTap = new CheckForTap();
}
···
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
} else {
···
checkForLongClick(0);
}
上面的代碼卸例,主角有兩個(gè):
1.創(chuàng)建、執(zhí)行CheckForTap線程并延遲100ms
2.調(diào)用checkForLongClick()方法
我們先進(jìn)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());
}
}
為mPrivateFlags取消PFLAG_PREPRESSED標(biāo)記
發(fā)現(xiàn)最終它也要調(diào)用checkForLongClick()
立刻追進(jìn)這個(gè)方法里,我猜測(cè)是要觸發(fā)長(zhǎng)按了(看名字猜的哈哈)
private void checkForLongClick(int delayOffset) {
if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) {
mHasPerformedLongPress = false;
if (mPendingCheckForLongPress == null) {
mPendingCheckForLongPress = new CheckForLongPress();
}
mPendingCheckForLongPress.rememberWindowAttachCount();
postDelayed(mPendingCheckForLongPress,
ViewConfiguration.getLongPressTimeout() - delayOffset);
}
}
不是長(zhǎng)按事件處理肌毅,又套了一層筷转。先將mHasPerformedLongPress再次設(shè)為false,為了確保長(zhǎng)按一定沒(méi)有被觸發(fā)過(guò)
這里又發(fā)送了一個(gè)延時(shí)線程,主角是CheckForLongPress
private final class CheckForLongPress implements Runnable {
···
@Override
public void run() {
···
if (performLongClick()) {
mHasPerformedLongPress = true;
}
···
}
}
讀谷爸的代碼就是爽呀悬而,performLongClick()呜舒,返回還是布爾值,為true 將mHasPerformedLongPress= true.標(biāo)記長(zhǎng)按已被觸發(fā)
其實(shí)看這段代碼我一直在找一個(gè)主角 onLongClick().
public boolean performLongClick() {
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);
boolean handled = false;
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnLongClickListener != null) {
handled = li.mOnLongClickListener.onLongClick(View.this);
}
if (!handled) {
handled = showContextMenu();
}
if (handled) {
performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
}
return handled;
}
一眼就找到了主角onLongClickListener笨奠,判斷是否添加了OnLongClickListener袭蝗,如果添加了則執(zhí)行回調(diào)調(diào)用onLongClick方法,并且返回true般婆。
最終返回處理狀態(tài) handled 表示長(zhǎng)按事件是否被消費(fèi)到腥,到此長(zhǎng)按事件處理就結(jié)束了。
但是蔚袍!蛋蛋是!此處應(yīng)該被打板子 - -乡范!
一開(kāi)始有一個(gè)isInScrollingContainer的判斷,進(jìn)入checkForLongClick方法的延時(shí)時(shí)間不一樣啤咽,原本我慣性理解為在滾動(dòng)容器中處理的時(shí)間要比不在
滾動(dòng)容器中的觸發(fā)時(shí)間長(zhǎng)晋辆,還在納悶這么搞的意義何在?多別扭宇整。剛才想起來(lái)了這段代碼:
postDelayed(mPendingCheckForLongPress,
ViewConfiguration.getLongPressTimeout() - delayOffset);
我好傻瓶佳,這樣把延時(shí)時(shí)間一減最后整個(gè)時(shí)間軸總長(zhǎng)度不就一樣了嗎都是500ms. 靠!不對(duì)直覺(jué)告訴我不是500ms
從滾動(dòng)容器中的邏輯進(jìn)來(lái)鳞青,CheckForTap線程先延時(shí)了100ms才觸發(fā),觸發(fā)后調(diào)用checkForLongClick方法給出延時(shí)補(bǔ)償100ms
那在checkForLongClick 方法中減去延時(shí)補(bǔ)償霸饲,只減去了100ms为朋。
那就是如果在滾動(dòng)容器中觸發(fā)長(zhǎng)按時(shí)間為400ms
而在非滾動(dòng)容器中觸發(fā)長(zhǎng)按時(shí)間為500ms.- - 好像區(qū)別也不大,但是為毛要這么玩呢~(強(qiáng)迫癥)
2.ACTION_MOVE
···
if (!pointInView(x, y, mTouchSlop)) {
//移除回掉-移除的是CheckForTap贴彼,在滾動(dòng)容器內(nèi)的延時(shí)線程
// Outside button
removeTapCallback();
//判斷mPrivateFlags 是否被設(shè)置了PFLAG_PRESSED標(biāo)記(是否被點(diǎn)擊)潜腻,如果是則說(shuō)明長(zhǎng)按的延時(shí)處理線程已經(jīng)啟動(dòng)
則移除長(zhǎng)按回掉線程
if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
//移除即將執(zhí)行的長(zhǎng)按回掉
// Remove any future long press/tap checks
removeLongPressCallback();
setPressed(false);
}
}
這里有兩個(gè)移除回掉的方法要執(zhí)行:
removeTapCallback()
removeLongPressCallback()
結(jié)合上面的分析,這里調(diào)用順序和對(duì)PFLAG_PRESSED的判斷其實(shí)是必然的器仗,暫且先放一下現(xiàn)看看移除了什么
/**
移除定時(shí)線程~ 跪死在英語(yǔ)上了融涣。。精钮。
* Remove the tap detection timer.
*/
private void removeTapCallback() {
if (mPendingCheckForTap != null) {
mPrivateFlags &= ~PFLAG_PREPRESSED;
removeCallbacks(mPendingCheckForTap);
}
}
1 . 判斷是否創(chuàng)建了延時(shí)線程CheckForTap
2 . 對(duì)mPrivateFlags取消 PFLAG_PREPRESSED標(biāo)記(還有一個(gè)地方取消此標(biāo)記是在CheckForTap線程執(zhí)行后威鹿,所以上面對(duì)于PFLAG_PREPRESSED的解釋是沒(méi)錯(cuò)的)
3 . 從線程隊(duì)列中移除mPendingCheckForTap,只要沒(méi)有被執(zhí)行就一定會(huì)被取消轨香,也就中斷了長(zhǎng)按事件
既:在滾動(dòng)容器內(nèi)如果再按下后100ms內(nèi)你滑出了view的范圍馬上就會(huì)丟失長(zhǎng)按事件
后面調(diào)用removeLongPressCallback()為什么先判斷mPrivateFlags是否包含PFLAG_PRESSED的標(biāo)記忽你?
因?yàn)椴皇窃跐L動(dòng)容器內(nèi)觸發(fā),或者CheckForTap 已經(jīng)執(zhí)行了臂容,這兩者都會(huì)進(jìn)入CheckForLongPress線程中科雳,也都會(huì)調(diào)用setPressed(true,x,y)
在其中為mViewFlags添加PFLAG_PRESSED標(biāo)記,長(zhǎng)按事件將要被處理。
所以如果mPrivateFlags包含著PFLAG_PRESSED標(biāo)記脓杉,則說(shuō)明CheckForLongPress已被開(kāi)啟糟秘,需要移除CheckForLongPress線程,中斷回掉球散。
既:在非滾動(dòng)容器內(nèi)觸發(fā)長(zhǎng)按尿赚,在500ms內(nèi)移出了view的范圍則會(huì)取消長(zhǎng)按事件的處理
3.ACTION_UP
核心代碼:
···
//是否將執(zhí)行長(zhǎng)按事件- mPrivateFlags包含PFLAG_PREPRESSED標(biāo)記,長(zhǎng)按的延時(shí)檢測(cè)正在執(zhí)行
boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
//已被按下 或者觸發(fā)長(zhǎng)按事件
if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
···
//不是長(zhǎng)按事件蕉堰,也沒(méi)有忽略后續(xù)的upEvent
if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
···
//移除長(zhǎng)按檢測(cè)線程
removeLongPressCallback();
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
//如果添加PerformClick到線程隊(duì)列凌净,此線程中會(huì)調(diào)用 performClick()
//如果失敗則立即直接調(diào)用performClick()
if (!post(mPerformClick)) {
performClick();
}
//這么做地目的一時(shí)不太明白,希望有同學(xué)為我解惑呀
}
}
if (mUnsetPressedState == null) {
mUnsetPressedState = new UnsetPressedState();
}
//如果需要處理長(zhǎng)按事件屋讶,啟動(dòng)延時(shí)64ms的線程
//同上添加到線程隊(duì)列失敗則立即調(diào)用run方法確保達(dá)到目的
if (prepressed) {
postDelayed(mUnsetPressedState,
ViewConfiguration.getPressedStateDuration());
} else if (!post(mUnsetPressedState)) {
mUnsetPressedState.run();
}
//移除長(zhǎng)按檢測(cè)(長(zhǎng)按事件還未執(zhí)行)
removeTapCallback();
}
···
- 首先來(lái)看這兩行代碼冰寻,我覺(jué)這兩行的信息兩最大
boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed)
記得前面只有兩個(gè)地方取消這個(gè)標(biāo)記,一個(gè)是CheckFotTap的延時(shí)線程執(zhí)行時(shí)丑婿,另一個(gè)是在觸摸移除view范圍后
這里觸發(fā)了ACTION_UP,只能是前者性雄。
根據(jù)ACTION_DOWN中的判斷
1.在滾動(dòng)容器中觸發(fā)ACRION_DOWN,在100ms后才會(huì)調(diào)用setPressed(true,x,y)
,將PFLAG_PRESSED標(biāo)記添加給mViewFlags
所以在100ms 內(nèi)觸發(fā)ACTION_UP prepressed = true
2.在非滾動(dòng)容器中觸發(fā)ACTION_DOWN,會(huì)立即調(diào)用setPressed(true,x,y)
,并開(kāi)啟處理長(zhǎng)按事件回掉的延時(shí)線程
所以在500ms內(nèi)觸發(fā)ACTION_UP 將會(huì)改變 prepressed = true
100ms是非常短的羹奉,人的手指一般都無(wú)法在100ms內(nèi)觸發(fā)秒旋,所以下面的if判斷基本一直是滿足的。
再來(lái)看
if (!mHasPerformedLongPress && !mIgnoreNextUpEvent)
mHasPerformedLongPress什么時(shí)候?yàn)閠rue呢诀拭,還記的上面出現(xiàn)過(guò)的CheckForLongPress線程迁筛,只有在此線程中執(zhí)行preformLongClick()
返回true時(shí)mHasPerformedLongPress才等于true.如果沒(méi)有設(shè)置長(zhǎng)按監(jiān)聽(tīng)或者在ACTION_DOWN 觸發(fā)后的500ms內(nèi)(在滾動(dòng)容器內(nèi)為400ms)
觸發(fā)ACTION_UP mHasPerformedLongPress都為false ,會(huì)進(jìn)入這個(gè)if.(注意這里僅時(shí)進(jìn)入此判斷,并沒(méi)對(duì)長(zhǎng)按事件處理有任何干預(yù))進(jìn)入if里面
先執(zhí)行了removeLongPressCallback();
移除CheckForLongPress線程耕挨,也就是說(shuō)這里就中斷了長(zhǎng)按事件的檢測(cè)细卧,長(zhǎng)按事件從這里就結(jié)束了不會(huì)再有回掉尉桩。
接著創(chuàng)建了PerformClick線程,并添加了添加到消息隊(duì)列失敗判斷贪庙。最終都會(huì)進(jìn)入performClick()
方法中.
進(jìn)入一探究竟
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;
}
久違了蜘犁,終于看到onClick.毫無(wú)疑問(wèn)這里就是對(duì)onClickListener監(jiān)聽(tīng)的回掉。
疑問(wèn)止邮?上面調(diào)用了removeLongPressCallback()
方法移除了長(zhǎng)按事件的檢測(cè)这橙,那是不是如果執(zhí)行了onClick 長(zhǎng)按事件就不會(huì)執(zhí)行了呢?
當(dāng)然不是导披,只要onLongClick執(zhí)行返回未false onClick就可以被觸發(fā)屈扎。所以他們是可以同時(shí)存在的。
在CheckForLongPress的run方法中是這樣為 mHasPerformedLongPress改變狀態(tài)的
if (performLongClick()) {
mHasPerformedLongPress = true;
}
可以看到撩匕,mHasPerformedLongPress只取決于performLongClick的返回值鹰晨,并不關(guān)心方法內(nèi)是否執(zhí)行onLongClick.
所以只要onLongClikc返回false,mHasPerformedLongPress就已然為false.在ACTION_UP中就滿足
!mHasPerformedLongPress = true
從而會(huì)執(zhí)行onClick
最后還剩兩個(gè)操作
1.創(chuàng)建UnsetPressedState 并最終調(diào)用setPressed(false)
,取消mViewFlags的PFLAG_PRESSED標(biāo)記,并刷新視圖。
2.執(zhí)行removeTapCallback()
移除CheckForTap延時(shí)線程(在100ms內(nèi)觸發(fā)了ACTION_UP,才會(huì)取消)
對(duì)于單個(gè)View整個(gè)事件分發(fā)流程的源碼學(xué)習(xí)就結(jié)束了止毕,收獲還是蠻大的,對(duì)于添加監(jiān)聽(tīng)和點(diǎn)擊事件整個(gè)處理流程已然有了非常清晰的了解模蜡。之前也是看了很多的這方面的博客的
到底不如自己細(xì)細(xì)分析來(lái)的深刻,而且于都源碼一開(kāi)始挺費(fèi)勁的扁凛,但是稍稍摸索就會(huì)發(fā)現(xiàn)其樂(lè)無(wú)窮之后根本停不下來(lái)哩牍。
在學(xué)習(xí)過(guò)程中發(fā)現(xiàn)和知名博主以前記錄源碼稍有差別,因?yàn)楣鹊苍诟侣铩?br> 本篇源碼SDK版本為23.
學(xué)習(xí)源碼地址 :事件分發(fā)機(jī)制的學(xué)習(xí)-View篇