1. 程式人生 > >Android 觸控模式(Touch Mode)

Android 觸控模式(Touch Mode)

什麼是焦點?

在非觸屏手機時代或電腦上,我們通常需要用鍵盤、 滑鼠、軌跡球(trackball)與介面進行互動,當互動的時候必須使目標控制元件獲得焦點(比如高亮起來),這樣使用者才會注意到是什麼控制元件接受輸入。而如果是在觸屏時代,使用者可以直接用手指點選控制元件,這個時候就沒必要將目標高亮了(即獲取焦點)。這也就是接下來我們要講的觸控模式(Touch Mode)。

觸控模式

當用戶使用方向鍵或軌跡球導航使用者介面時,必須聚焦到可操作專案上(如按鈕),以便使用者看到將接受輸入的物件。 但是,如果裝置具有觸控功能且使用者開始通過觸控介面與之互動,則不再需要突出顯示專案或聚焦到特定檢視物件上。 因此,有一種互動模式稱為“觸控模式”(Touch Mode)

觸控模式是使用者和手機進行互動時view層次結構的一個狀態。代表了最近一次的互動是否是通過觸控式螢幕發生的,因為在Android裝置上還存在別的互動方式,比如鍵盤、等等。

對於支援觸控功能的裝置,當用戶觸控式螢幕幕時,裝置會立即進入觸控模式。無論何時,只要使用者點選方向鍵或滾動軌跡球,裝置就會退出觸控模式並找到一個檢視使其獲得焦點。 現在,使用者可在不觸控式螢幕幕的情況下繼續與使用者介面互動。

整個系統(所有視窗和 Activity)都將保持觸控模式狀態。要查詢當前狀態,您可以呼叫View#isInTouchMode() 來檢查裝置目前是否處於觸控模式。

焦點和觸控模式

在觸控模式下,沒有焦點,沒有選擇。同樣,任何已聚焦的控制元件當進入觸控模式時都變為未聚焦狀態。而當使用軌跡球和鍵盤時,就會立即離開觸控模式,控制元件就會變成聚焦的狀態。

現在我們知道了焦點不可以存在於觸控模式了吧,但是這並不完全正確,焦點其實是可以一種特殊的方式存在於觸控模式中的,我們稱之為聚焦( focusable)。這種特殊的模式是專門為可接收文字輸入的控制元件建立的,比如EditText。這就是為什麼使用者可以直接輸入文字到文字框中,而不必先用手指選擇其文字框的原因。

在觸控模式中,任何控制元件只要是可聚焦(focusable )的狀態,當用戶點選其控制元件時,該控制元件就會接收到其焦點。如果是不可聚焦的,點選控制元件將不會接收到焦點。如下所示,當用戶點選EditText時,EditText會接收到焦點:

image_1b45qt521k4j1uje2us11bdr479.png-51.6kB

對於支援觸控功能的裝置,當用戶觸控式螢幕幕時,裝置會立即進入觸控模式。 自此以後,只有 isFocusableInTouchMode() 為 true 的檢視才可聚焦,如文字編輯小部件EditText。其他可觸控的檢視(如按鈕Button)在使用者觸控時不會獲得焦點;按下時它們只是觸發點選偵聽器。

在觸控模式下,只有少部分的控制元件預設是可聚焦的狀態,例如EditText。可以通過setFocusableInTouchMode或xml中android:focusableInTouchMode設定控制元件是否可聚焦。

setFocusableInTouchMode 和 setFocusable

很多人對這兩個方法有疑問其實很簡單:

  • setFocusable:設定控制元件是否能獲取焦點。可以通過isFocusable()獲取其狀態。
  • setFocusableInTouchMode:在觸控模式下,設定控制元件是否允許聚焦。可以通過isFocusableInTouchMode() 獲取其狀態。

在使用鍵盤或軌跡球的情況下,只有setFocusable為true的控制元件,才可以獲取焦點(選中時高亮)。而在觸控模式下,setFocusable為true,並無法保證控制元件可以獲取焦點。setFocusable為true只能保證在非觸控模式下,該控制元件可以允許獲取焦點。如果想在在觸控模式中,改變控制元件是否允許聚焦,請使用setFocusableInTouchMode進行更改。

從上面我們也可以看出,不管是否在觸控模式下,控制元件獲取焦點的前提是isFocusable()為true。而在觸控模式下,只有isFocusable()和isFocusableInTouchMode()都為ture的情況下,控制元件才允許聚焦。

下面通過設定setFocusableInTouchMode和setFocusable的先後順序,檢視控制元件的焦點狀態:

button.setFocusableInTouchMode(false);
button.setFocusable(true);
Log.d("cryc","isFocusable  "+button.isFocusable()+" isFocusableInTouchMode "+ button.isFocusableInTouchMode());
//isFocusable  true  isFocusableInTouchMode false 

button.setFocusableInTouchMode(true);
button.setFocusable(false);
Log.d("cryc","isFocusable  "+button.isFocusable()+" isFocusableInTouchMode "+ button.isFocusableInTouchMode());
//isFocusable  false isFocusableInTouchMode false

button.setFocusable(true);
button.setFocusableInTouchMode(false);
Log.d("cryc","isFocusable  "+button.isFocusable()+" isFocusableInTouchMode "+ button.isFocusableInTouchMode());
//isFocusable  true isFocusableInTouchMode false

button.setFocusable(false);
button.setFocusableInTouchMode(true);
Log.d("cryc","isFocusable  "+button.isFocusable()+" isFocusableInTouchMode "+ button.isFocusableInTouchMode());
//isFocusable  true isFocusableInTouchMode true

通過上面我們發現如下的規律:
setFocusableInTouchMode為true,會使isFocusable也變為true,而setFocusableInTouchMode為false並不影響isFocusable。
setFocusable為false,會使isFocusableInTouchMode變為false,而setFocusable為true並不影響isFocusableInTouchMode。

下面我列出了各種常用控制元件的預設初始狀態:

控制元件 Focusable FocusableInTouchMode Clickable LongClickable
View false false false false
TextView false false false false
EditText true true true true
Button true false true false
ImageButton true false true false
ImageView false false false false
CheckBox true false true false
RadioButton true false true false
ProgressBar false false false false
LinearLayout false false false false
RelativeLayout false false false false
其他Layout都幾乎一樣 false false false false

從上面我們可以看出,大部分的控制元件FocusableInTouchMode屬性都為false。只有類似EditText這種控制元件才為true,因為EditText需要提供在沒使用者點選的條件下,彈出一個軟鍵盤進行輸入的功能。

控制元件可聚焦的注意點

我們現在有如下的佈局,設定如下的兩個Button都是可聚焦的:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="com.example.mytesttwo.MainActivity">
    <Button
        android:layout_width="match_parent"
        android:text="one"
        android:focusableInTouchMode="true"
        android:id="@+id/btnOne"
        android:layout_height="wrap_content" />
    <Button
        android:layout_width="match_parent"
        android:text="two"
        android:focusableInTouchMode="true"
        android:id="@+id/btnTwo"
        android:layout_height="wrap_content" />
</LinearLayout>

並設定了兩個button點選和焦點的監聽事件:

btnOne.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                  Log.d("cryc","btnOne onClick");
            }
        });
        btnOne.setOnFocusChangeListener(new View.OnFocusChangeListener() {
            @Override
            public void onFocusChange(View v, boolean hasFocus) {
                Log.d("cryc","btnOne onFocusChange "+ hasFocus);
            }
        });
        btnTwo.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.d("cryc","btnTwo onClick");
            }
        });
        btnTwo.setOnFocusChangeListener(new View.OnFocusChangeListener() {
            @Override
            public void onFocusChange(View v, boolean hasFocus) {
                Log.d("cryc","btnTwo onFocusChange "+hasFocus);
            }
        });

當首次進後,應用介面是下面這樣的
image_1b45rk6cod691q12nsfqae1t1r13.png-39.3kB
這時候焦點在按鈕one上,使按鈕的背景變成了橘黃色,Log也打印出了資訊。

btnOne onFocusChange true 

這時候當我們點選按鈕two的時候,焦點切換到按鈕two上:

image_1b45rlg4kov1u291r5617gg12vq1t.png-35.1kB

btnOne onFocusChange false
btnTwo onFocusChange true

注意Log打印出的資訊,當用戶點選button two的時候,點選事件並沒有響應,只有焦點事件響應了。這時候有的開發者會覺得很奇怪?通常我們不是點選一下就可以響應按鈕的點選事件嗎? 因為當前的按鈕處於可聚焦的狀態,要讓按鈕響應事件必須點選兩下:

  • 第一下,使焦點聚焦於button上。
  • 第二下,才是真正響應點選的事件。

從上面我們也可以看出有時候設定focusableInTouchMode為true真的不能亂用,不然可能導致上面的情況發生。

在預設擁有可聚焦屬性的EditText也是如此,當進入帶有EditText控制元件的頁面時,EditText會自動獲取焦點(黃色邊框+游標閃爍),但此時輸入法軟鍵盤是不會自動彈起的(除非你特殊設定),也是需要使用者點選EditText才會彈起輸入法:
image_1b45rpb4fjvvc0g772gil1hjn2n.png-20.8kB
java EditTextOne onFocusChange true
但是當我們已經處在頁面中時,使用者只要點選一次第二個的輸入框時,軟鍵盤就會彈起。不要以為第二個的輸入框接收了點選事件(onClick),它接收的還是焦點事件,之所以會彈起,因為系統內部做了處理。

EditTextOne  onFocusChange false
EditTextTwo  onFocusChange true

那系統內部如何做處理的呢?我認為可能是在onTouchEvent方法中做處理,注意了只要有觸控控制元件的操作都會觸發onTouchEvent方法,當手指接觸控制元件的那一刻先響應的是onTouchEvent方法,然後觸發的才是onFocusChange方法或onClick方法。

注意:要想讓控制元件不觸發onTouchEvent方法,設定控制元件disable是沒有效果的,控制元件的CLICKABLE、LONG_CLICKABLE、CONTEXT_CLICKABLE這三個狀態必須都為false才行。還有一個方法就是可以通過父控制元件進行截獲輸入事件。

在專案中,一進入一個頁面, EditText預設就會自動獲取焦點。那麼如何取消這個預設行為呢?有人在EditText的父級控制元件中找一個,設定成可聚焦的狀態:android:focusableInTouchMode=”true” 把EditText的焦點進行截獲,使焦點轉移到父控制元件上:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:focusableInTouchMode="true"
   >
    <EditText
       android:layout_width="match_parent"
        android:layout_height="wrap_content" />
</LinearLayout>

雖然解決了當前的問題,但是這裡卻有個隱含的風險,在有些低版本的系統中,當你把焦點轉移到父控制元件的身上時,如果從後臺切換到前臺,會導致整個介面傳送抖動。而這個bug通常很少人會得出原因,因此在轉移焦點的時候要特別注意。

由於設定了focusableInTouchMode屬性後會引起和android正常互動行為的不一致,所以android建議我們保守地使用這個屬性,在你確定要用它之前最好三思而後行。