1. 程式人生 > >事件分發系列—View中的dispatchTouchEvent和onTouchEvent分析

事件分發系列—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的回撥。

相關推薦

事件分發系列ViewdispatchTouchEventonTouchEvent分析

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筆記-ViewdispatchTouchEventonTouchEvent原始碼解析

View中的dispatchTouchEvent比較簡單,因為view只能是最底層的,所以沒有向下傳遞的機制,也沒有攔截機制。 其dispatchTouchEvent方法原始碼如下: /** * Pass the touch screen

Mysql viewmergetemportary

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: 表示使用者在移動(手指或者其他)     

AndroidViewViewGroup事件分發攔截機制完美分析

出自: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

一、簡介   最近馬三換了一家大公司工作,公司制度規範了一些,因此平時的業餘時間多了不少。但是人卻懶了下來,最近這一個月都沒怎麼研究新技術,部落格寫得也是拖拖拉拉,週六周天就躺屍在家看帖子、看小說,要麼就是吃雞,唉!真是罪過罪過。希望能從這篇部落格開始有些改善吧,儘量少玩耍

AndroiddispatchTouchEvent,onInterceptTouchEventonTouchEvent的區別

dispatchTouchEvent:決定了事件是否繼續分發下去和是否響應事件,false:繼續分發,true:不繼續分發,此次事件到此結束,也不會有任何控制元件執行onTouchEvent方法。 onInterceptTouchEvent:決定了是否攔截該事件,false:不攔截,true:攔

AndroiddispatchTouchEvent()、onInterceptTouchEvent()onTouchEvent()

Android中觸控事件傳遞過程中最重要的是dispatchTouchEvent()、onInterceptTouchEvent()和onTouchEvent()方法。這個是困擾初學者的問題之一,我開始也是。這裡記錄一下dispatchTouchEvent()、onInter

AndroidView事件分發機制

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 謝謝博主 ====================================================================