事件分發系列—View中的dispatchTouchEvent和onTouchEvent分析
dispatchTouchEvent
話不多說直接上原始碼
/**
* Pass the touch screen motion event down to the target view, or this
* view if it is the target.
* 將螢幕的按壓事件傳遞給目標view,或者當前view即目標view
*
* @param event The motion event to be dispatched.
* 需要分發的事件
*
* @return True if the event was handled by the view, false otherwise.
* 如果返回true表示這個事件被這個view處理了,否則反
*/
public boolean dispatchTouchEvent(MotionEvent event) {
//系統除錯分析相關,沒有影響
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(event, 0);
}
//過濾是不是能夠傳遞這個touch事件
if (onFilterTouchEventForSecurity(event)) {
//首先判斷我們在使用該view的時候是否有實現OnTouchListener,如果有實現就判斷當前的view
//狀態是不是ENABLED,如果實現的OnTouchListener的onTouch中返回true,並處理事件,則
//返回true,這個事件在此處理了。
if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
mOnTouchListener.onTouch(this, event)) {
return true;
}
//如果沒有在程式碼裡面setOnTouchListener的話,就判斷View自身的onTouchEvent方法有沒有
//處理,沒有處理最後返回false,處理了返回true;
if (onTouchEvent(event)) {
return true;
}
}
//系統除錯分析相關,沒有影響
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
}
//如果即沒有setOnTouchListener,也沒有在onTouchEvent中處理,就返回false
return false;
}
從上面的原始碼可以看出:在View的分發事件方法dispatchTouchEvent中,處理分發的順序是實現OnTouchListener的onTouch(),之後是當前View的onTouchEvent(event)方法。
onTouchEvent
onTouchEvent的原始碼有點長,所以要沉下心來仔細閱讀。事件消耗和事件處理都是返回true,事件消耗就相當於佔坑不拉屎,雖然有點噁心哈,事件處理當然就是佔坑拉屎。
在Android的觸控訊息中,已經實現了三種監測,它們分別是
1)pre-pressed:對應的語義是使用者輕觸(tap)了螢幕
2)pressed:對應的語義是使用者點選(press)了螢幕
3)long pressed:對應的語義是使用者長按(long press)了螢幕
下圖是觸控訊息隨時間變化的時間軸示意圖:
相關引用來自> http://www.linuxidc.com/Linux/2012-08/67979.htm
瞭解onTouchEvent就現需要了解的方法和類
- CheckForTap類
該類實現了Runnable介面,在run函式中設定觸控標識,並重新整理Drawable的狀態,同時用於傳送一個檢測長按事件的非同步延遲訊息,程式碼如下:
private final class CheckForTap implements Runnable {
public void run() {
// 進入該函式,說明已經過了ViewConfiguration.getTapTimeout()時間,
// 即pre-pressed狀態結束,宣告觸控進入pressed狀態
mPrivateFlags &= ~PREPRESSED;
mPrivateFlags |= PRESSED;
refreshDrawableState(); // 重新整理控制元件的背景Drawable
// 如果長按檢測沒有被去使能,則傳送一個檢測長按事件的非同步延遲訊息
if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) {
postCheckForLongClick(ViewConfiguration.getTapTimeout());
}
}
}
private void postCheckForLongClick(int delayOffset) {
mHasPerformedLongPress = false;
// 例項化CheckForLongPress物件
if (mPendingCheckForLongPress == null) {
mPendingCheckForLongPress = new CheckForLongPress();
}
mPendingCheckForLongPress.rememberWindowAttachCount();
// 呼叫PostDelayed函式傳送長按事件的非同步延遲訊息
postDelayed(mPendingCheckForLongPress,
ViewConfiguration.getLongPressTimeout() - delayOffset);
}
- CheckForLongPress類
該類定義了長按操作發生時的響應處理,同樣實現了Runnable介面
class CheckForLongPress implements Runnable {
private int mOriginalWindowAttachCount;
public void run() {
// 進入該函式,說明檢測到了長按操作
if (isPressed() && (mParent != null)
&& mOriginalWindowAttachCount == mWindowAttachCount) {
if (performLongClick()) {
mHasPerformedLongPress = true;
}
}
}
public void rememberWindowAttachCount() {
mOriginalWindowAttachCount = mWindowAttachCount;
}
}
public boolean performLongClick() {
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);
boolean handled = false;
if (mOnLongClickListener != null) {
// 回撥使用者實現的長按操作監聽函式(OnLongClickListener)
handled = mOnLongClickListener.onLongClick(View.this);
}
if (!handled) {
// 如果OnLongClickListener的onLongClick返回false
// 則需要繼續處理該長按事件,這裡是顯示上下文選單
handled = showContextMenu();
}
if (handled) {
// 長按操作事件被處理了,此時應該給使用者觸覺上的反饋
performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
}
return handled;
}
瞭解完,我們來看看onTouchEvent的實現
/**
* Implement this method to handle touch screen motion events.
* 如果需要處理螢幕產生的事件流需要實現這個方法
*
* @param event The motion event.
*
* @return True if the event was handled, false otherwise.
* 如果返回true表示這個處理了這個事件,false則反
*/
public boolean onTouchEvent(MotionEvent event) {
//viewFLags用來記錄當前View的狀態
final int viewFlags = mViewFlags;
//如果當前View狀態為DISABLED,如果不清楚DISABLED是一種什麼狀態那你應該用過
//setEnabled(boolean enabled)這個方法,DISABLED就是ENABLED相反的狀態。
//DISABLED = 0x00000020 ,ENABLED_MASK = 0x00000020
if ((viewFlags & ENABLED_MASK) == DISABLED) {
if (event.getAction() == MotionEvent.ACTION_UP && (mPrivateFlags & PRESSED) != 0) {
//如果View的狀態是被按壓過,且當擡起事件產生,重置View狀態為未按壓,重新整理Drawable的狀態
mPrivateFlags &= ~PRESSED;
refreshDrawableState();
}
//如果當前View是一個DISABLED狀態,且當前View是一個可點選或者是可長按的狀態則當前事件在
//此消耗不做處理,返回true。
return (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));
}
----------
//TouchDelegate是一個事件處理邏輯封裝的一個類,也就是說Touch事件處理被委託了,那麼就交由
//mTouchDelegate.onTouchEvent處理,如果返回true,則事件被處理了,則不會向下傳遞
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
----------
//如果當前View的狀態是可點選或者是可長按的,就對事件流進行細節處理
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
switch (event.getAction()) {
case MotionEvent.ACTION_UP:
//PREPRESSED = 0x02000000
boolean prepressed = (mPrivateFlags & PREPRESSED) != 0;
//如果是pressed狀態或者是prepressed狀態,才進行處理
if ((mPrivateFlags & PRESSED) != 0 || prepressed) {
// take focus if we don't have it already and we should in
// touch mode.
//如果設定了獲取焦點,那麼呼叫requestFocus獲得焦點
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.
//在釋放之前給使用者顯示View的prepressed的狀態,狀態需要改變為
//PRESSED,並且需要將背景變為按下的狀態為了讓使用者感知到
mPrivateFlags |= PRESSED;
refreshDrawableState();
}
// 是否處理過長按操作了,如果是,則直接返回
if (!mHasPerformedLongPress) {
//如果不是長按的話,僅僅是一個Tap,所以移除長按的回撥
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.
//UI子執行緒去執行click,為了讓click事件開始的時候其他視覺發
//生變化不影響。
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
//如果post訊息失敗,直接呼叫處理click事件
if (!post(mPerformClick)) {
performClick();
}
}
}
----------
if (mUnsetPressedState == null) {
mUnsetPressedState = new UnsetPressedState();
}
if (prepressed) {
//ViewConfiguration.getPressedStateDuration() 獲得的是按下效
//果顯示的時間,由PRESSED_STATE_DURATION常量指定,在2.2中為125
//毫秒,也就是隔了125毫秒按鈕的狀態重置為未點選之前的狀態。目的是讓使用者
//感知到click的效果
postDelayed(mUnsetPressedState,
ViewConfiguration.getPressedStateDuration());
} else if (!post(mUnsetPressedState)) {
//如果通過post(Runnable runnable)方式呼叫失敗,則直接呼叫
mUnsetPressedState.run();
}
//移除Tap的回撥 重置View的狀態
removeTapCallback();
}
break;
case MotionEvent.ACTION_DOWN:
mHasPerformedLongPress = false;
//在觸控事件中執行按鈕相關的動作,如果返回true則表示已經消耗了down
if (performButtonActionOnTouchDown(event)) {
break;
}
// Walk up the hierarchy to determine if we're inside a scrolling container.
//判斷是否在一個滾動的容器內
boolean isInScrollingContainer = isInScrollingContainer();
// 如果父容器是一個可滾動的容器
if (isInScrollingContainer) {
mPrivateFlags |= PREPRESSED;
//將view的狀態變為PREPRESSED,檢測是Tap還是長按事件
if (mPendingCheckForTap == null) {
mPendingCheckForTap = new CheckForTap();
}
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
} else {
// Not inside a scrolling container, so show the feedback right away
//直接將view狀態轉化為PRESSED,更新Drawable
mPrivateFlags |= PRESSED;
refreshDrawableState();
//是否是長按事件的判斷
checkForLongClick(0);
}
break;
//接收到系統發出的ACTION_CANCLE事件時,重置狀態
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();
// 如果移動超出了按鈕的範圍
if (!pointInView(x, y, mTouchSlop)) {
// Outside button
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;
}
對於onTouchEvent總的來說,首先受到事件首先判斷當前View的狀態是否為DISABLED,如果是則只需要簡單的做一些狀態的重置,不對事件做細節處理。如果不是DISABLED,就需要對事件細節進行處理,這時候又半路來個程咬金TouchDelegate,如果mTouchDelegate不為空,且返回了true,就表示該事件流有人給你代勞處理了,後面的分析View自己也不用做了。最後如果沒人攔截處理,那就得View自己來。
下面開始是View自己處理事件流的邏輯過程描敘,即switch判斷事件分支的處理:
ACTION_DOWN
判斷是否在觸控事件中執行按鈕相關的動作,如果是,直接跳出,不是則繼續判斷當前View是否在一個可滑動的容器中,如果是則判斷是否是一個點選tab事件,還是長按事件的檢查,如果不是則直接轉化狀態為PRESSED並判斷是否為長按事件。ACTION_MOVE
判斷移動的點是否在當前View中,如果不在其中且當前狀態為PRESSED則重置非PRESSED,且移除長按的回撥。ACTION_UP
當擡起事件產生,首先判斷View的狀態是pressed狀態或者是prepressed狀態(也就是按過),才進行處理,如果是prepressed狀態則變為pressed,並更新Drawable,然後判斷在Down中的mHasPerformedLongPress標誌,有沒有變為true,也就是有沒有產生執行長按事件,如果沒有,則把這個事件流當做一個click事件做處理,也就是執行performClick中的程式碼,執行完click中的程式碼,最後重置View的狀態和重新整理Drawable。ACTION_CANCLE
當系統傳送一個action_cancle的事件,則重置View的狀態為非PRESSED,重新整理Drawable的狀態,且移除Tab的回撥。
相關推薦
事件分發系列—View中的dispatchTouchEvent和onTouchEvent分析
dispatchTouchEvent 話不多說直接上原始碼 /** * Pass the touch screen motion event down to the target view, or this * view if
【朝花夕拾】Android自定義View篇之(六)Android事件分發機制(中)從原始碼分析事件分發邏輯及經常遇到的一些“詭異”現象
前言 轉載請註明,轉自【https://www.cnblogs.com/andy-songwei/p/11039252.html】謝謝! 在上一篇文章【【朝花夕拾】Android自定義View篇之(
Android筆記-View中的dispatchTouchEvent和onTouchEvent原始碼解析
View中的dispatchTouchEvent比較簡單,因為view只能是最底層的,所以沒有向下傳遞的機制,也沒有攔截機制。 其dispatchTouchEvent方法原始碼如下: /** * Pass the touch screen
Mysql view中merge和temportary
merge: 引用檢視的語句和定義檢視的語句合併生成一條新的查詢語句,查詢實體表 temportary: 定義檢視時建立一張臨時表,搜尋時查詢的是該臨時表 通常不用定義,mysql 自己選擇 詳細參考 View
Android事件分發機制——View(一)
/** * Implement this method to handle touch screen motion events. * * @param event The motion event. * @return True if the event was
事件分發機制的詳解及原始碼分析
事件分發機制詳解 MotionEvent 主要分為以下幾個事件型別: ACTION_DOWN 手指開始觸控到螢幕的那一刻響應的是DOWN事件 ACTION_MOVE 接著手指在螢幕上移動響應的是MOVE事件 ACTION_UP 手指從螢幕上鬆開的那一刻響
Touch事件傳遞流程、事件分發中的onTouch 和onTouchEvent 有什麼區別,又該如何使用?
Touch事件傳遞流程1.Touch事件型別 Touch事件被封裝成MotionEvent,使用者當前的touch事件主要型別有: ACTION_DOWN: 表示使用者開始觸控 ACTION_MOVE: 表示使用者在移動(手指或者其他)
Android中View和ViewGroup事件分發攔截機制完美分析
出自:http://www.cnblogs.com/linjzong/p/4191891.html Touch事件分發中只有兩個主角:ViewGroup和View。Activity的Touch事件事實上是呼叫它內部的ViewGroup的Touch事件,可以直接當成Vie
13.View的事件分發機制——dispatchTouchEvent詳解
在前面的第二篇文章中,我們提過,View的事件分發是一種委託思想:上層委託下層,父容器委託子元素來處理這個流程。接下來,我們就將深入去學習View的事件分發機制。 1.事件的傳遞流程 事件,在A
事件分發理解:在整個介面的觸控事件中分別處理某些view
軟鍵盤: 1,showSoftInput(view, InputMethodManager.SHOW_FORCED); 可通過第二個引數flags設定SHOW_IMPLICIT效果就是觸控edittext外任何區域就隱藏軟鍵盤 設定flags為SHOW_FORCED觸控任何區域不隱藏
Android View 的事件分發原理和滑動衝突分析
作為一名Android 開發者,每天接觸最多的就是 View 了。Android View 雖然不是四大元件,但其並不比四大元件的地位低。而 View 的核心知識點事件分發機制則是不少剛入門同學的攔路虎,也是面試過程中基本上都會問的。理解 View 的事件能夠讓你寫出更好自定義 View 以及解決滑動衝突。
【Unity遊戲開發】用C#和Lua實現Unity中的事件分發機制EventDispatcher
一、簡介 最近馬三換了一家大公司工作,公司制度規範了一些,因此平時的業餘時間多了不少。但是人卻懶了下來,最近這一個月都沒怎麼研究新技術,部落格寫得也是拖拖拉拉,週六周天就躺屍在家看帖子、看小說,要麼就是吃雞,唉!真是罪過罪過。希望能從這篇部落格開始有些改善吧,儘量少玩耍
Android中dispatchTouchEvent,onInterceptTouchEvent和onTouchEvent的區別
dispatchTouchEvent:決定了事件是否繼續分發下去和是否響應事件,false:繼續分發,true:不繼續分發,此次事件到此結束,也不會有任何控制元件執行onTouchEvent方法。 onInterceptTouchEvent:決定了是否攔截該事件,false:不攔截,true:攔
Android中的dispatchTouchEvent()、onInterceptTouchEvent()和onTouchEvent()
Android中觸控事件傳遞過程中最重要的是dispatchTouchEvent()、onInterceptTouchEvent()和onTouchEvent()方法。這個是困擾初學者的問題之一,我開始也是。這裡記錄一下dispatchTouchEvent()、onInter
Android中View事件分發機制
View事件分發機制 今天要寫一寫Android中比較重要的一個核心,View事件分發機制。那麼事件分發機制是什麼,為什麼要寫這個呢, 下面將一一講解出來。 前言 相信大家對Android基礎知識都已經有所瞭解啦,因為畢竟Android已經涼了,應該也沒有多少新
Android的事件分發(dispatchTouchEvent),攔截(onInterceptTouchEvent)與處理(onTouchEvent)
在Android中,View的結構是樹狀的,所以,當觸發觸控事件的時候,其事件傳遞也是從上之下一層層的傳遞。下面我們結合例子來一點點進行分析。 首先,我們需要了解事件處理中的幾個方法: 1、在ViewGroup中,事件分為dispatchTouchEvent(事件的分發)
View,ViewGroup,Activity三者的OnTouchEvent事件分發
首先確定有三種,由內向外依次為: 1、View自己的onTouchEvent 2、ViewGroup的onTouchEvent,由於要管理它的子View的onTouchEvent,所以多了個onInterceptTouchEvent(鼓勵過載這個而不是dispatchTouchEvent,因為後者是對Vie
Android從零開搞系列:自定義View(9)事件分發+事件攔截(滑動衝突)
我和一幫應屆生同學維護了一個公眾號:IT面試填坑小分隊。旨在幫助應屆生從學生過度到開發者,並且每週樹立學習目標,一同進步! 寫在前面 今天用了一天的時間去再一次梳理了一遍,事件分發和事件攔截。用了這麼長時間倒不是因為理解深刻,,而是順便看了3
android 事件分發,解決由於listview中實時重新整理,導致子view點選事件失效
近期由於個人的某些因素作怪,導致沒有很好地總結和積累,主要是最近一段時間,大多數接觸的都是第三方的sdk ,在一些介面問題上造成了很多困擾,很是麻煩,並且說明文件也不詳細,所以每每遇到一些問題都要等待很久才能解決。 好了,廢話不多說了。下面開始今天的
關於JAVA中事件分發和監聽機制實現的程式碼例項-絕對原創實用
轉載:http://blog.csdn.net/5iasp/article/details/37054171 謝謝博主 ====================================================================