Android onTouchEvent, onClick及onLongClick的調用機制

在android系統中,我們有2種方法進入界面查看內容,一種是通過按鍵切換焦點,然後按"確認"鍵進入,還有一種就是通過觸摸或者鼠標點擊,這種點擊是沒有焦點控制的,也不需要焦點控制,

比如現在的觸摸屏手機,界面上是沒有焦點的,我們點擊就進入了,根本沒有焦點的概念。

在模擬器上跑也是,比如我們創建含有多個button的一個Activity,某個button的處罰會進入另外一個Activity.

如果我們是通過按鍵進入的話,退出到第一個Activity時候是有焦點的,但是如果是通過鼠標就不會再有焦點。

同樣我們測試ListView,ListActivity也是一樣,鼠標點擊是沒有焦點,根本上說是沒有這個概念,我們想進入哪個直接點哪個就OK了,也不需要焦點,但是按鍵必須有焦點控制我才知道是哪個。

我們可以通過源碼分析得到解釋:

case MotionEvent.ACTION_UP:下面有討論

android系統默認就有2種不同的處理方式,當然我們可以根據需要進行相關修改,下面我們就來討論。

 

一.基本概念:

基本的監聽消息

OnClickListener
OnTouchModeChangeListener
OnTouchListener
OnFocusChangeListener

 

監聽消息處理函數

onKeyDown 用於相應按鍵按下;
onKeyMultiple 用於響應按鍵重複點擊;
onKeyUp 用於響應按鍵釋放
onClick 來自View.OnClickListener 當點擊這個Item(在觸摸模式),或者當光標聚集在這個Item上時按下“確認”鍵,導航鍵,或者軌跡球。會被調用
onTouchModeChanged 用於響應觸摸屏事件(包括鼠標點擊操作)
onTouch 來自View.OnFocusChangeListener 在這個Item的範圍內點觸的時候,會被調用
onFocusChange 當光標移到或離開這個Item,會被調用

 

狀態控制相關函數

setFocusable    --------- isClickable
setClickable    ---------  isFocusable(區別isFocused,一個是能否有焦點,一個是是不是焦點)
setFocusableInTouchMode ---- isFocusableInTouchMode(區別isInTouchMode)
requestFocusFromTouch ------ isInTouchMode

 

參考API解釋:

public final boolean requestFocusFromTouch ()
Since: API Level 1 Call this to try to give focus to a specific view or to one of its descendants. This is a special variant of requestFocus() that will allow views that are not focuable in touch mode to request focus when they are touched.

Returns
Whether this view or one of its descendants actually took focus


public boolean isInTouchMode ()
Since: API Level 1 Returns whether the device is currently in touch mode. Touch mode is entered once the user begins interacting with the device by touch, and affects various things like whether focus is always visible to the user.

Returns
Whether the device is in touch mode.

 


public final boolean isFocusableInTouchMode ()
Since: API Level 1 When a view is focusable, it may not want to take focus when in touch mode. For example, a button would like focus when the user is navigating via a D-pad so that the user can click on it, but once the user starts touching the screen, the button shouldn't take focus

Related XML Attributes
android:focusableInTouchMode
Returns
Whether the view is focusable in touch mode.

 

通過isFocusable()這個方法我們可以知道view是否具有接受焦點的資格,
也可以通過setFocusable().來設置view接受焦點的資格,
對應在觸摸模式下,你可以調用isFocusableInTouchMode().來獲知是否有焦點來響應點觸,注意是資格,
也可以通過setFocusableInTouchMode().來設置是否有焦點來響應點觸的資格.
區別requestFocusFromTouch和isInTouchMode


系統框架控制焦點移動到另一個組件的算法是在某一方向上鄰近的組件,在極個別情況下,默認的算法可能不符合開發者的預想要求,在這種情況下,你可以覆寫下列XML屬性的佈局文件:

nextFocusDown , nextFocusLeft , nextFocusRight ,和nextFocusUp 設置他們的值來明確

 

通常如果你想宣佈用戶界面具有焦點的資格 (如果這個界面在傳統上是沒有的),可以在xml佈局裏去加上的android:focusable的屬性,並設置它的值,

您也可以宣佈在觸摸模式下具有焦點的資格,同樣也只在xml裏添android:focusableInTouchMode.的屬性,並設置它的值.

當用戶請求在某個界面聚集焦點時,會調用requestFocus().這個方法監聽到焦點活動(獲得焦點或失去焦點都會被通知),會調用onFocusChange(),這個方法,這也是上節所討論的Event Listeners

 

總結:對於UI控件事件的處理,需要設定相應的監聽器,並實現相應的事件處理程序。

這兒有兩種實現方法:一是定義一個OnClickListener 類的實例,並使用setOnClickListener等綁定監聽器;

二是用Activity去實現OnClickListener接口,並作爲它的一部分,這樣會更方便,省去了加載額外的類和對象的時間。對於支持觸摸屏的手機,可以設定觸摸模式的UI,可以使用isInTouchMode()來獲得觸摸模式的狀態。UI處理的另一個重點是焦點的設定及其切換。

焦點設置:通過setFocusable或者setFocusableInTouchMode設置可以接受焦點,通過isFocusable或isFocusableInTouchMode獲取是否可以接受焦點;

焦點切換:編寫XML佈局文件的nextFocusDown 等屬性設置

 

在Android中,onClick、onLongClick的觸發是和ACTION_DOWN及ACTION_UP相關的,
在時序上,如果我們在一個View中同時覆寫了onClick、onLongClick及onTouchEvent的話,
onTouchEvent是最先捕捉到ACTION_DOWN和ACTION_UP事件的,其次纔可能觸發onClick或者onLongClick。

 

在Android平臺上,捕獲用戶在界面上的觸發事件有很多種方法,View類就提供這些方法。你在使用各種View視圖來佈局界面時,會發現幾個公用的回調方法來捕捉有用的UI觸發事件,當事件在某個View對象上被觸發時,這些方法會被系統框架通過這個對象所調用,
例如:當一個View(如一個Button)被點擊(被鼠標或者觸摸),onTouchEvent()方法會在該對象上被調用,所以爲了捕獲和處理事件,必須去繼承某個類,並重載這些方法,以便自己定義具體的處理邏輯,顯然,你更容易明白,爲什麼在你使用View類時會嵌套帶有這些回調方法的接口類,這些接口稱爲event listeners,它是你去獲取UI交互事件的工具在你繼承View類,以便建立一個自定義組,也許你想繼承Button 你會更普遍使用事件監聽來捕捉用戶的互動,在種情況下,你可以使用類的event handlers.來預定義事件的處理方法。

 

記住:我們所關注的事件肯定是發生在高亮聚集的焦點,它從總視圖(頂級的)被一級一級的向下傳遞,直到我們想要關注的組件,當焦點聚集在這個視圖(或視圖中的子視圖)時,你能夠使用dispatchKeyEvent() 作爲一種代替方法,來捕獲在視圖上的按鍵事件,你還可以使用onKeyDown()和onKeyUp().來捕獲所有事件內的交互活動

如果設備有觸摸功能,那麼,當用戶與界面的交互就不再需要有一個高亮在組件上,或一個焦點在view上,因此,模式的互動名爲"觸摸模式"。對於一個觸摸設備,一旦有用戶接觸屏幕時,該設備將進入觸摸模式.在點觸某個View後,只有的它的方法isFocusableInTouchMode()返回爲真時,纔會有聚集焦點,如文本編輯工具。其他的界面只可以點觸,但不會聚集焦點(高亮),如button被點觸時就不會聚集焦

 

我們測試代碼主要是以Button,ImageButton和EdieText控件作爲測試:
各個控件默認的焦點相關屬性參考下圖:

 

 

當然我們可以在相關控件定義的XML文件中去定義相關屬性:
比如我們可以定義Button的XML文件屬性 android:focusableInTouchMode="true"設置有焦點來響應點觸的資格,和我們在代碼中手動調用setFocusableInTouchMode是一樣的效果.

 

設定焦點:

我們在一個ViewGroup(比如一個Layout)中手動設定一指定控件爲focus焦點,比如如下:
bt2是以普通控件句柄
bt2.setFocusable(true);
bt2.setFocusableInTouchMode(true);
bt2.requestFocus();
實際上我們Button控件默認的isFocusable就是true,所以實際上對於設定一個Button控件爲焦點,我們只需要
bt2.setFocusableInTouchMode(true);//這步必須要(不過後來我驗證了,這步不要好像也可以設置控件焦點)
bt2.requestFocus();
當然對於EditView來說,默認的isFocusable是true,isFocusableInTouchMode也是true,所以我們指定EditView爲焦點就更簡單了:
bt2.requestFocus();

 

分析View源碼中的onTouchEvent源碼處理:(\android_src\frameworks\base\core\java\android\view):
還可以參考下面的一篇博文:
 case MotionEvent.ACTION_UP:
 boolean focusTaken = false;
                        if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
                            focusTaken = requestFocus();//設置焦點
                        }
 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();
                                }
                            }

知道如果isFocusableInTouchMode是false,就是沒有設置獲取觸摸焦點屬性時候,我們的View會主動調用performClick觸發onClick()回調,但是如果isFocusableInTouchMode是true,就不會主動觸發onClick,而是像按鍵處理一樣去設置焦點。


這就是下面我們測試的其中一種情況,也是DV6300-T主界面出現的問題,後來我們修改了。

void onFocusChange(View v, boolean hasFocus)回調處理:
實際上通過打印圖片我們就知道了,在按下按鍵或者觸摸點擊時候切換焦點,
一般是會調用onFocusChange回調2次,一次是取消原來的焦點View,這個時候hasFocus是false,並且調用getCurrentFocus返回的null然後就會再調用一次,設置當前焦點View,這時候hasFocus是true,調用getCurrentFocus返回的就是傳入的參數v,也就是當前焦點控件

 

當我們的控件(比如Button)設置了相關屬性:
setFocusable(true)
setFocusableInTouchMode(true)
那麼當我們按鍵處理的時候肯定是正常的,但是當我們點擊該控件時候,肯定會調用onFocusChange回調,前面我們分析了是不會觸發onClick回調的
也就是說,這種情況下我們只是把焦點移到該控件,需要我們再在該控件點擊一下,纔會觸發onClick事件.
所以6300-T的主界面我們做了一個處理,達到的效果就是我們用鼠標點擊控件時候,不僅焦點移動到該控件,還會觸發onClick事件:

認沒有設置setFocusableInTouchMode時候是直接會觸發onClick事件,而且是沒有焦點的概念。

setFocusableInTouchMode(true)後會設置焦點,但是不會觸發onClick事件。
我們的做法就是在onFocusChange實現我們自己的處理,如下:
public void onFocusChange(View v, boolean hasFocus) {
   if (hasFocus) {  
    if (v.isInTouchMode())//判斷是否是觸摸,鼠標點擊來切換焦點
    {  
     ((Button)v).performClick();  
    }  
   }
 }
}
調用performClick方法主動觸發控件的onClick事件(和View源碼onTouchEvent的MotionEvent.ACTION_UP一樣處理)。實際上我們也可以利用performClick方法來模擬任何控件的鼠標點擊處理等(一般不需要我們這樣做而已)
可以參考下面博文:

 

在實現上面效果時候,如果用v.isFocusableInTouchMode替代v.isInTouchMode會有問題,會影響按鍵處理,比如按上下按鍵只是切換焦點,但是
卻任然會觸發onClick事件.
原因就是如果控件設置了setFocusableInTouchMode爲true,那麼isFocusableInTouchMode永遠是true,除非是重新設置了setFocusableInTouchMode
這就是isFocusableInTouchMode和isInTouchMode的區別
setFocusableInTouchMode ---- isFocusableInTouchMode(Whether the view is focusable in touch mode)
requestFocusFromTouch ------ isInTouchMode   (判斷Whether the device is in touch mode)
對應關係:
isFocusableInTouchMode只是判斷是否有焦點來響應點觸的資格
isInTouchMode就是判斷是否在TouchMode爲true下點擊了該控件(這是我自己的理解,可能有誤,最好還是參考API的英文解釋)

 

參考源碼就在:

E:\JAVA\Android\DV6300-T測試代碼\Touch_Focus_Test

 

時序調用圖如下:

基本上下按鍵切換焦點onFocusChange



在onFocusChange中利用isFocusedInTouchMode作爲是否調用performClick接口的條件,出現問題,上下按鍵時候實際上只是切換焦點,結果卻因爲調用performClick而觸發了控件的onClick


在onFocusChange中利用isInTouchMode作爲是否調用performClick接口的條件,上下按鍵正常,只是單純切換焦點



在onFocusChange中利用isInTouchMode作爲是否調用performClick接口的條件,並且用鼠標單擊控件,會切換焦點,並且在onFocusChange中isInTouchMode處理,觸發控件的onClick


設置控件的觸摸焦點控制爲false,setFocusableInTouchMode(false),單擊控件,是不會觸發onFocusChange接口的,但是上面分析anroid源碼知道,會去利用performClick觸發onClick,並且會導致目前沒有任何控件是焦點


沒有在onFocusChange增加相關的條件判斷,只是打印相關信息,正常鼠標點擊控件,會切換焦點到該控件



沒有在onFocusChange增加相關的條件判斷,和上面一種情況一樣,也就是說鼠標單擊控件第一次只是切換焦點,然後在點擊一次,纔會去觸發控件的onClick。

這是在onFocusChange中沒做任何處理正常情況,而我們DV6300-T要求的事一次鼠標點擊就要觸發onClick,所以我們手動又在onFocusChange中增加了isInTouchMode作爲是否調用performClick接口的條件,這樣來手動觸發



參考本地網絡資料:

E:\JAVA\Android\資料\網絡資料\android焦點控制

 

參考博文:

Android onTouchEvent, onClick及onLongClick的調用機制

Android UI事件處理

android 控件焦點問題

Android開發之《Android應用開發揭祕》UI事件彙總

Android學習筆記--UI事件監聽器及其處理程序

 

onInterceptTouchEvent和onTouchEvent調用時序

Android onTouchEvent, onClick及onLongClick的調用機制

發佈了22 篇原創文章 · 獲贊 19 · 訪問量 23萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章