Android事件分發機制——View(一)
阿新 • • 發佈:2019-02-05
下面我們來拆分一下上面的原始碼首先執行一個if判斷語句/** * Implement this method to handle touch screen motion events. * * @param event The motion event. * @return True if the event was handled, false otherwise. */ public boolean onTouchEvent(MotionEvent event) { final int viewFlags = mViewFlags; /** * 如果一個View是disabled, 並且該View是Clickable或者longClickable, * onTouchEvent()就不執行下面的程式碼邏輯直接返回true, 表示該View就一直消費Touch事件 */ if ((viewFlags & ENABLED_MASK) == DISABLED) { // 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)); } /** * 如果此View有觸碰事件處理代理,那麼將此事件交給代理處理 */ if (mTouchDelegate != null) { if (mTouchDelegate.onTouchEvent(event)) { return true; } } /** * 如果不可點選(既不能單擊,也不能長按)則直接返回false */ if (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) { switch (event.getAction()) { case MotionEvent.ACTION_UP: boolean prepressed = (mPrivateFlags & PREPRESSED) != 0; if ((mPrivateFlags & PRESSED) != 0 || prepressed) { // take focus if we don't have it already and we should in // touch mode. boolean focusTaken = false; /** * 是否需要獲得焦點及用變數focusTaken設定是否獲得了焦點. * 如果我們還沒有獲得焦點,但是我們在觸控屏下又可以獲得焦點,那麼則請求獲得焦點 */ if (isFocusable() && isFocusableInTouchMode() && !isFocused()) { focusTaken = requestFocus(); } /** * 判斷是否進行了長按事件的返回值情況,如果為false則移除長按的延遲訊息並繼續往下執行 */ if (!mHasPerformedLongPress) { // 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) { mPrivateFlags |= PRESSED; refreshDrawableState(); /** * ViewConfiguration.getPressedStateDuration() 獲得的是按下效 * 果顯示的時間,由PRESSED_STATE_DURATION常量指定,在2.2中為125毫秒 */ postDelayed(mUnsetPressedState, ViewConfiguration.getPressedStateDuration()); } else if (!post(mUnsetPressedState)) { // If the post failed, unpress right now mUnsetPressedState.run(); } removeTapCallback(); } break; case MotionEvent.ACTION_DOWN: if (mPendingCheckForTap == null) { mPendingCheckForTap = new CheckForTap(); } mPrivateFlags |= PREPRESSED; /** * 給mHasPerformedLongPress設定初始值為false */ mHasPerformedLongPress = false; /** * 傳送一個延遲訊息延遲時間為ViewConfiguration.getTapTimeout()在2.2的原始碼中此值為115毫秒 * 到達115毫秒後會執行CheckForTap()方法,如果在這115毫秒之間使用者觸控移動了,則 * 刪除此訊息.否則執行按下狀態,在CheckForTap()中檢查長按. */ postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout()); break; case MotionEvent.ACTION_CANCEL: mPrivateFlags &= ~PRESSED; refreshDrawableState(); removeTapCallback(); break; case MotionEvent.ACTION_MOVE: final int x = (int) event.getX(); final int y = (int) event.getY(); // Be lenient about moving outside of buttons int slop = mTouchSlop; /** * 當手指在View上面滑動超過View的邊界, */ if ((x < 0 - slop) || (x >= getWidth() + slop) || (y < 0 - slop) || (y >= getHeight() + slop)) { // Outside button /** * 如果手指滑動超過Vie的邊界則移除DOWN事件中設定的檢測 */ removeTapCallback(); if ((mPrivateFlags & PRESSED) != 0) { // Remove any future long press/tap checks removeLongPressCallback(); // Need to switch from pressed to not pressed mPrivateFlags &= ~PRESSED; refreshDrawableState(); } } break; } return true; } return false; }
在這裡要特別注意的是此方法中if(((viewFlags & CLICKABLE) == CLICKABLE ||(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE))它的範圍這裡把中間的程式碼省略如下:if ((viewFlags & ENABLED_MASK) == DISABLED) { // 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 )); }
從上面的簡化程式碼中我們可以看出只要是進入了if判斷語句則onTouchEvent一定會返回true即消費事件,並且進入此if語句的條件為 此View是可以點選的或者是可以長按的。 下面我們來拆分一下上面的原始碼首先執行一個if判斷語句public boolean onTouchEvent(MotionEvent event) { 。。。。。。。。。。。 此處有省略 if (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) { switch (event.getAction()) { 。。。。。。。。。。。 此處有省略 } return true; } return false; }
if ((viewFlags & ENABLED_MASK) == DISABLED) {
// 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 ));
}
在上面的註釋中已經對其進行了說明,這裡單獨拿出來再強調一下-----如果一個View是disabled, 並且該View是Clickable或者longClickable, onTouchEvent()就不執行下面的程式碼邏輯直接返回true, 表示該View就一直消費Touch事件,這一點從上面的程式碼可以看出,如果一個enabled的View,並且是clickable或者longClickable的,onTouchEvent()會執行下面的程式碼邏輯並返回true,這一點從上面的省略程式碼片段可以得出。
綜上,一個clickable或者longclickable的View是一直消費Touch事件的,而一般的View既不是clickable也不是longclickable的(即不會消費Touch事件,只會執行ACTION_DOWN而不會執行ACTION_MOVE和ACTION_UP) Button是clickable的,可以消費Touch事件,但是我們可以通過setClickable()和setLongClickable()來設定View是否為clickable和longClickable。
接著我們來分析一下onTouchEvent中的事件上面的程式碼中有比較詳細的註釋,我在這裡再分析一下
ACTION_DOWN:
case MotionEvent.ACTION_DOWN:
if (mPendingCheckForTap == null) {
mPendingCheckForTap = new CheckForTap();
}
mPrivateFlags |= PREPRESSED;
mHasPerformedLongPress = false;
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
break;
在這個方法中首先給mPrivateFlags設定一個PREPRESSED的標識,然後設定為mHasPerformedLongPress設定一個初始值false,接著會執行一個延遲
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
在這裡ViewConfiguration.getTapTimeout()的值為115毫秒(注意以上原始碼包括時間常量都是2.2原始碼中,其他原始碼可能會稍有不同)這個延遲有什麼作用呢?在給定的TapTimeout時間之內,使用者的觸控沒有移動,就當作使用者是想點選,而不是滑動.具體的做法是,將 CheckForTap的例項mPendingCheckForTap新增時訊息隊例中,延遲執行。如果在這tagTimeout之間使用者觸控移動了,則刪除此訊息.否則執行按下狀態.然後檢查長按。
經過115毫秒的延遲後會執行CheckForTap方法,這個方法是幹什麼的呢?來看下原始碼 /**
* ACTION_DOWN事件延遲115毫秒後呼叫
*/
private final class CheckForTap implements Runnable {
public void run() {
mPrivateFlags &= ~PREPRESSED;
mPrivateFlags |= PRESSED;
refreshDrawableState();
/**
* 如果View支援長按事件即View是LONG_CLICKABLE的則傳送一個長按事件的檢測
*/
if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) {
postCheckForLongClick(ViewConfiguration.getTapTimeout());
}
}
}
這個方法可以看到如果View是LONG_CLICKABLE的就是執行postCheckForLongClick(ViewConfiguration.getTapTimeout())這個方法來檢測長按事件,但是一般的View不是LONG_CLICKABLE的,可能有的人會有疑問,如果View不是LONG_CLICKABLE的怎麼執行長按事件啊?此時我們需要呼叫setOnLongClickListener實現OnLongClickListener介面
原始碼如下:
/**
* Register a callback to be invoked when this view is clicked and held. If this view is not
* long clickable, it becomes long clickable.
*
* @param l The callback that will run
*
* @see #setLongClickable(boolean)
*/
public void setOnLongClickListener (OnLongClickListener l) {
if (!isLongClickable()) {
setLongClickable( true);
}
mOnLongClickListener = l;
}
從原始碼中我們可以看到設定了OnLongClickListener後如果這個View不是LONG_CLICKABLE的,那麼就把它設定成LONG_CLICKABLE的。這樣我們回到CheckForTap方法在View是LONG_CLICKABLE的情況下就會呼叫postCheckForLongClick方法,這個方法的原始碼如下 private void postCheckForLongClick(int delayOffset) {
/**
* 設定mHasPerformedLongPress為false表示長按事件還未觸發
*/
mHasPerformedLongPress = false;
if (mPendingCheckForLongPress == null) {
mPendingCheckForLongPress = new CheckForLongPress();
}
mPendingCheckForLongPress.rememberWindowAttachCount();
/**
* 此delayOffset是從上面的CheckForTap類中傳過來的值為ViewConfiguration.getTapTimeout()
* ViewConfiguration.getLongPressTimeout()在2.2中為500毫秒,也就是經過500-115毫秒後會執行CheckForLongPress方·· * 法在CheckForLongPress方法中會呼叫執行長按事件的方法,由於在ACTION_DOWN事件中有一個延遲訊息延遲115毫秒後 * 執行CheckForTap中的run方法所以這裡500-115+115=500也就是說從按下起經過500毫秒會觸發長按事件的執行
*/
postDelayed(mPendingCheckForLongPress,ViewConfiguration.getLongPressTimeout() - delayOffset);
}
在上面的方法會有一個延遲經過500-115毫秒後會執行CheckForLongPress方法。class CheckForLongPress implements Runnable {
private int mOriginalWindowAttachCount;
/**
* 因為等待形成長按的過程中,介面可能發生變化如Activity的pause及restart,這個時候,長按應當失效.
* View中提供了mWindowAttachCount來記錄View的attach次數.當檢查長按時的attach次數與長按到形成時.
* 的attach一樣則處理,否則就不應該再當前長按. 所以在將檢查長按的訊息新增時隊伍的時候,要記錄下當前的windowAttach *Count.
*/
public void run() {
if (isPressed() && (mParent != null)
&& mOriginalWindowAttachCount == mWindowAttachCount) {
if (performLongClick()) {
/**
* 執行長按事件後返回值為true,設定mHasPerformedLongPress為true此時會遮蔽點選事件
*/
mHasPerformedLongPress = true;
}
}
}
public void rememberWindowAttachCount() {
mOriginalWindowAttachCount = mWindowAttachCount;
}
}
從CheckForLongPress的run方法中可以看到如果performLongClick()的返回值為true mHasPerformedLongPress才為true,那麼我們看看performLongClick它的做了哪些動作呢?我們來看看原始碼
/**
* Call this view's OnLongClickListener, if it is defined. Invokes the context menu
* if the OnLongClickListener did not consume the event.
*
* @return True there was an assigned OnLongClickListener that was called, false
* otherwise is returned.
*/
public boolean performLongClick() {
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);
boolean handled = false;
/**
* 到了重點可以看到在這裡會執行我們為View設定的長按事件的回撥,這裡的mOnLongClickListener就是我們自己給View設定的長按的監聽,
* 從這裡也可以得出一個結論即長按事件是在ACTION_DOWN中執行的
*/
if (mOnLongClickListener != null) {
handled = mOnLongClickListener.onLongClick(View.this);
}
if (!handled) {
handled = showContextMenu();
}
if (handled) {
performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
}
return handled;
}
終於來了個重點我們看到在其中有個判斷if (mOnLongClickListener != null) {
handled = mOnLongClickListener.onLongClick(View.this);
}
也就是說如果你設定了長按的監聽,那麼mOnLongClickListener!=null此時就會執行我們重寫的onLongClick()方法,這裡我們也得出一個結論:
即長按事件是在ACTION_DOWN中執行的。到這裡我們可以總結一下:首先當執行ACTION_DOWN事件後會設定一個PREPRESSED標識,如果這次點選持續115毫秒後就會發送一個檢測長按的延遲任務,這個任務的延遲時間是500-115毫秒,這個115毫秒就是檢測PREPRESSED所經歷的時間,所以這樣算一下就可以知道當按鈕從按下的那一刻起經歷了500毫秒就會觸發長按事件(注意這個Android 2.2中的原始碼,其它的系統的時間會稍有差異) 通過以上的分析我們還可以得出如下結論:
1、如果此時設定了長按的回撥,則執行長按時的回撥,且如果長按的回撥返回true;才把mHasPerformedLongPress置為ture;
2、否則,如果沒有設定長按回調或者長按回調返回的是false;則mHasPerformedLongPress依然是false; 一般的View預設是不消費touch事件的,我們要想執行點選事件必須要呼叫setOnClickListener()來設定OnClickListener介面,我們看看這個方法的原始碼就知道了public void setOnClickListener (OnClickListener l) {
if (!isClickable()) {
setClickable( true);
}
mOnClickListener = l;
}
看到沒?當我們設定了onClickListener時如果isClickable()=false,就執行 setClickable(true)。