Android事件處理機(jī)制(1)-輸入事件

最近希望能系統(tǒng)的學(xué)習(xí)并整理Android的事件分發(fā)流程。這是第一篇文章,當(dāng)然是從Android Develpers開發(fā)文檔中學(xué)習(xí)啦。

Android系統(tǒng)中,為了在用戶和視圖界面之間進(jìn)行交互,需要截獲用戶的觸摸事件。安卓的View類定義了一系列的嵌套接口來幫助開發(fā)者來輕松定義回調(diào),這就是事件偵聽器(event listener)。
事件偵聽器在大部分的應(yīng)用場景中可以滿足偵聽用戶與UI交互的需要,但某些場景需要構(gòu)建自定義組件,這就需要使用事件處理程序(event handlers)來定義組件默認(rèn)的事件行為。

事件偵聽器

事件偵聽器是View 類中包含一個回調(diào)方法的接口。 當(dāng)用戶與 UI 項目之間的交互觸發(fā)已注冊此視圖的偵聽器時,Android 框架將調(diào)用這些方法。

各事件偵聽器接口包含的回調(diào)方法如下:

  • onClick()
    View.OnClickListener 中。 當(dāng)用戶觸摸項目(處于觸摸模式下)時,或者使用導(dǎo)航鍵或軌跡球聚焦于項目,然后按適用的“Enter”鍵或按下軌跡球時,將調(diào)用此方法。

  • onLongClick()
    View.OnLongClickListener 中。 當(dāng)用戶觸摸并按住項目(處于觸摸模式下)時,或者使用導(dǎo)航鍵或軌跡球聚焦于項目,然后按住適用的“Enter”鍵或按住軌跡球(持續(xù)一秒鐘)時,將調(diào)用此方法。

  • onFocusChange()
    View.OnFocusChangeListener 中。 當(dāng)用戶使用導(dǎo)航鍵或軌跡球?qū)Ш降交蜻h(yuǎn)離項目時,將調(diào)用此方法。

  • onKey()
    View.OnKeyListener 中。 當(dāng)用戶聚焦于項目并按下或釋放設(shè)備上的硬按鍵時,將調(diào)用此方法。

  • onTouch()
    View.OnTouchListener 中。 當(dāng)用戶執(zhí)行可視為觸摸事件的操作時,其中包括按下、釋放或屏幕上的任何移動手勢(在項目邊界內(nèi)),將調(diào)用此方法。

  • onCreateContextMenu
    View.OnCreateContextMenuListener 中。 當(dāng)(因持續(xù)“長按”而)生成上下文菜單時,將調(diào)用此方法。請參見菜單開發(fā)者指南中有關(guān)上下文菜單的闡述。

這些方法是其相應(yīng)接口的唯一成員。要定義其中一個方法并處理事件,請在 Activity 中實現(xiàn)嵌套接口或?qū)⑵涠x為匿名類。然后,將實現(xiàn)的實例傳遞給相應(yīng)的 View.set...Listener() 方法。相信安卓開發(fā)者對事件偵聽器的使用方法都很熟悉。

部分事件偵聽器的方法回調(diào)沒有返回值,部分則必須返回布爾值。原因參考如下:

  • onLongClick():此方法返回一個布爾值,表示您是否已處理完事件,以及是否應(yīng)該將它繼續(xù)傳下去。 也就是說,返回 true 表示您已經(jīng)處理事件且事件應(yīng)就此停止;如果您尚未處理事件和/或事件應(yīng)該繼續(xù)傳遞給其他任何點擊偵聽器,則返回 false。
  • onKey():此方法返回一個布爾值,表示您是否已處理完事件,以及是否應(yīng)該將它繼續(xù)傳下去。 也就是說,返回 true 表示您已經(jīng)處理事件且事件應(yīng)就此停止;如果您尚未處理事件和/或事件應(yīng)該繼續(xù)傳遞給其他任何按鍵偵聽器,則返回 false。
  • onTouch(): 此方法返回一個布爾值,表示偵聽器是否處理完此事件。重要的是,此事件可以擁有多個分先后順序的操作。 因此,如果在收到關(guān)閉操作事件時返回 false,則表示您并未處理完此事件,而且對其后續(xù)操作也不感興趣。 因此,您無需執(zhí)行事件內(nèi)的任何其他操作,如手勢或最終操作事件。

注:Android 會先調(diào)用事件處理程序,然后從類定義調(diào)用合適的默認(rèn)處理程序。 因此,從這些事件偵聽器返回 true 會停止將事件傳播到其他事件偵聽器,還會阻止回調(diào)視圖對象中的默認(rèn)事件處理程序。 因此,在返回 true 時請確保您要終止事件。

View的dispatchTouchEvent源碼解析

我們知道View都是通過dispatchTouchEvent方法來分發(fā)觸摸事件的,下面是View的相關(guān)代碼,注意只保留了主要邏輯。

    /**
     * Pass the touch screen motion event down to the target view, or this
     * view if it is the target.
     *
     * @param event The motion event to be dispatched.
     * @return True if the event was handled by the view, false otherwise.
     */
    public boolean dispatchTouchEvent(MotionEvent event) {
        ... //省略部分代碼

        if (onFilterTouchEventForSecurity(event)) {
            ...//省略部分代碼
            //noinspection SimplifiableIfStatement
            ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnTouchListener != null
                    && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) {
                result = true;
            }

            if (!result && onTouchEvent(event)) {
                result = true;
            }
        }
        ...//省略部分代碼
        return result;
    }

li.mOnTouchListener即為setOnTouchListener方法里賦值的對象,(mViewFlags & ENABLED_MASK) == ENABLED是判斷當(dāng)前點擊的控件是否enable,默認(rèn)按鈕都是enable的。上述兩個條件滿足,則會調(diào)用li.mOnTouchListener.onTouch方法。根據(jù)接下來的代碼可知,只有我們在onTouch方法中false,才能執(zhí)行onTouchEvent方法,進(jìn)而觸發(fā)onClick、onLongClick方法。

筆記:如果對一個View同時設(shè)置了OnTouchListener和OnClickListener,onTouch方法的執(zhí)行早于onClick方法,且onTouch方法的返回值會影響onClick方法的觸發(fā)。如果onTouch方法返回true,則onClick方法將不會被觸發(fā)。在默認(rèn)情況下onTouch方法返回false,可以接收View的界面內(nèi)發(fā)生的所有Touch事件,且此時可觸發(fā)onClick方法。onTouch事件一般包含幾個有先后順序的操作,常見的如(ACTION_DOWN,ACTION_UP)、(ACTION_DOWN,ACTION_MOVE...ACTION_MOVE,ACTION_UP)。一旦返回false則表示您并未處理完此事件,且對后續(xù)操作不感興趣,但是onTouch會按順序接收到完整的一系列Touch事件。

事件處理程序

當(dāng)從視圖構(gòu)建自定義組件時,能夠定義幾種用作默認(rèn)事件處理程序的回調(diào)方法。用于事件處理的常見回調(diào),包括

  • onKeyDown(int, KeyEvent):在發(fā)生新的按鍵事件時調(diào)用
  • onKeyUp(int, KeyEvent):在發(fā)生按鍵彈起事件時調(diào)用
  • onTrackballEvent(MotionEvent):在發(fā)生軌跡球運動事件時調(diào)用
  • onTouchEvent(MotionEvent):在發(fā)生觸摸屏運動事件時調(diào)用
  • onFocusChanged(boolean, int, Rect):在視圖獲得或失去焦點時調(diào)用

還有一些其他方法值得您注意,盡管它們并非 View 類的一部分,但可能會直接影響所能采取的事件處理方式。 因此,在管理布局內(nèi)更復(fù)雜的事件時,請考慮使用以下其他方法:

  • Activity.dispatchTouchEvent(MotionEvent):此方法允許Activity在分派給窗口之前截獲所有觸摸事件。
  • ViewGroup.onInterceptTouchEvent(MotionEvent):此方法允許ViewGroup監(jiān)視分派給子視圖的事件。
  • ViewParent.requestDisallowInterceptTouchEvent(boolean): 對父視圖調(diào)用此方法表明不應(yīng)使用 onInterceptTouchEvent(MotionEvent)截獲觸摸事件。

觸摸模式

當(dāng)用戶使用方向鍵或軌跡球?qū)Ш接脩艚缑鏁r,必須聚焦到可操作項目上(如按鈕),以便用戶看到將接受輸入的對象。 但是,如果設(shè)備具有觸摸功能且用戶開始通過觸摸界面與之交互,則不再需要突出顯示項目或聚焦到特定視圖對象上。 因此,有一種交互模式稱為“觸摸模式”。

對于支持觸摸功能的設(shè)備,當(dāng)用戶觸摸屏幕時,設(shè)備會立即進(jìn)入觸摸模式。 自此以后,只有isFocusableInTouchMode()為 true 的視圖才可聚焦,如文本編輯小部件。其他可觸摸的視圖(如按鈕)在用戶觸摸時不會獲得焦點;按下時它們只是觸發(fā)點擊偵聽器。

無論何時,只要用戶點擊方向鍵或滾動軌跡球,設(shè)備就會退出觸摸模式并找到一個視圖使其獲得焦點。 現(xiàn)在,用戶可在不觸摸屏幕的情況下繼續(xù)與用戶界面交互。

整個系統(tǒng)(所有窗口和 Activity)都將保持觸摸模式狀態(tài)。要查詢當(dāng)前狀態(tài),您可以調(diào)用isInTouchMode()來檢查設(shè)備目前是否處于觸摸模式。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。