1. 程式人生 > >Android軟鍵盤的隱藏顯示研究

Android軟鍵盤的隱藏顯示研究

原文地址 http://winuxxan.blog.51cto.com/2779763/522810

  Android是一個針對觸控式螢幕專門設計的作業系統,當點選編輯框,系統自動為使用者彈出軟鍵盤,以便使用者進行輸入。
    那麼,彈出軟鍵盤後必然會造成原有佈局高度的減少,那麼系統應該如何來處理佈局的減少?我們能否在應用程式中進行自定義的控制?這些是本文要討論的重點。

    一、軟鍵盤顯示的原理
    軟體盤的本質是什麼?軟鍵盤其實是一個Dialog!
    InputMethodService為我們的輸入法建立了一個Dialog,並且將該Dialog的Window的某些引數(如Gravity)進行了設定,使之能夠在底部或者全屏顯示。當我們點選輸入框時,系統對活動主視窗進行調整,從而為輸入法騰出相應的空間,然後將該Dialog顯示在底部,或者全屏顯示。
    二、活動主視窗調整


    android定義了一個屬性,名字為windowSoftInputMode, 用它可以讓程式可以控制活動主視窗調整的方式。我們可以在AndroidManifet.xml中對Activity進行設定。如:android:windowSoftInputMode="stateUnchanged|adjustPan"
    該屬性可選的值有兩部分,一部分為軟鍵盤的狀態控制,另一部分是活動主視窗的調整。前一部分本文不做討論,請讀者自行查閱android文件。
    模式一,壓縮模式
    windowSoftInputMode的值如果設定為adjustResize,那麼該Activity主視窗總是被調整大小以便留出軟鍵盤的空間。
我們通過一段程式碼來測試一下,當我們設定了該屬性後,彈出輸入法時,系統做了什麼。
    重寫Layout佈局:

  1. publicclass ResizeLayout extends LinearLayout{ 
  2.     privatestaticint count = 0
  3.     public ResizeLayout(Context context, AttributeSet attrs) { 
  4.         super(context, attrs); 
  5.     } 
  6.     @Override
  7.     protectedvoid onSizeChanged(int w, int h, int oldw, int oldh) {     
  8.         super.onSizeChanged(w, h, oldw, oldh); 
  9.         Log.e("onSizeChanged " + count++, "=>onResize called! w="+w + ",h="+h+",oldw="+oldw+",oldh="+oldh); 
  10.     } 
  11.     @Override
  12.     protectedvoid onLayout(boolean changed, int l, int t, int r, int b) { 
  13.         super.onLayout(changed, l, t, r, b); 
  14.         Log.e("onLayout " + count++, "=>OnLayout called! l=" + l + ", t=" + t + ",r=" + r + ",b="+b); 
  15.     } 
  16.     @Override
  17.     protectedvoid onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 
  18.         super.onMeasure(widthMeasureSpec, heightMeasureSpec); 
  19.         Log.e("onMeasure " + count++, "=>onMeasure called! widthMeasureSpec=" + widthMeasureSpec + ", heightMeasureSpec=" + heightMeasureSpec); 
  20.     } 

    我們的佈局設定為:

  1. <com.winuxxan.inputMethodTest.ResizeLayout
  2.     xmlns:android="http://schemas.android.com/apk/res/android"
  3.     android:id="@+id/root_layout"
  4.     android:layout_width="fill_parent"
  5.     android:layout_height="fill_parent"
  6.     android:orientation="vertical"
  7.     >
  8.     <EditText
  9.         android:layout_width="fill_parent"
  10.         android:layout_height="wrap_content"
  11.     />
  12.     <LinearLayout
  13.             android:id="@+id/bottom_layout"
  14.             android:layout_width="fill_parent"
  15.             android:layout_height="fill_parent"
  16.             android:orientation="vertical"
  17.             android:gravity="bottom">
  18.     <TextView
  19.         android:layout_width="fill_parent"
  20.         android:layout_height="wrap_content"
  21.         android:text="@string/hello"
  22.         android:background="#77777777"
  23.       />
  24.    </LinearLayout>
  25. </com.winuxxan.inputMethodTest.ResizeLayout>

    AndroidManifest.xml的Activity設定屬性:android:windowSoftInputMode = "adjustResize"
    執行程式,點選文字框,檢視除錯資訊:
    E/onMeasure 6(7960): =>onMeasure called! widthMeasureSpec=1073742144, heightMeasureSpec = 1073742024
    E/onMeasure 7(7960): =>onMeasure called! widthMeasureSpec=1073742144, heightMeasureSpec = 1073742025
    E/onSizeChanged 8(7960): =>onSizeChanged called! w=320,h=201,oldw=320,oldh=377
    E/onLayout 9(7960): =>OnLayout called! l=0, t=0,r=320,b=201
    從除錯結果我們可以看出,當我們點選文字框後,根佈局呼叫了onMeasure,onSizeChanged和onLayout。
    實際上,當設定為adjustResize後,軟鍵盤彈出時,要對主窗口布局重新進行measure和layout,而在layout時,發現視窗的大小發生的變化,因此呼叫了onSizeChanged。
    從下圖的執行結果我們也可以看出,原本在下方的TextView被頂到了輸入法的上方。

    

    模式二,平移模式
    windowSoftInputMode的值如果設定為adjustPan,那麼該Activity主視窗並不調整螢幕的大小以便留出軟鍵盤的空間。相反,當前視窗的內容將自動移動以便當前焦點從不被鍵盤覆蓋和使用者能總是看到輸入內容的部分。這個通常是不期望比調整大小,因為使用者可能關閉軟鍵盤以便獲得與被覆蓋內容的互動操作。
    上面的例子中,我們將AndroidManifest.xml的屬性進行更改:android: windowSoftInputMode = "adjustPan"

    重新執行,並點選文字框,檢視除錯資訊:
    E/onMeasure 6(8378): =>onMeasure called! widthMeasureSpec=1073742144, heightMeasureSpec=1073742200
    E/onMeasure 7(8378): =>onMeasure called! widthMeasureSpec=1073742144, heightMeasureSpec=1073742201
    E/onLayout 8(8378): =>OnLayout called! l=0, t=0,r=320,b=377
    我們看到:系統也重新進行了measrue和layout,但是我們發現,layout過程中onSizeChanged並沒有呼叫,這說明輸入法彈出前後並沒有改變原有佈局的大小。
    從下圖的執行結果我們可以看到,下方的TextView並沒有被頂到輸入法上方。

    

    事實上,當輸入框不會被遮擋時,該模式沒有對佈局進行調整,然而當輸入框將要被遮擋時,視窗就會進行平移。也就是說,該模式始終是保持輸入框為可見。如下圖,整個視窗,包括標題欄均被上移,以保證文字框可見。

    

    模式三 自動模式
    當屬性windowSoftInputMode被設定為adjustUspecified時,它不被指定是否該Activity主視窗調整大小以便留出軟鍵盤的空間,或是否視窗上的內容得到螢幕上當前的焦點是可見的。系統將自動選擇這些模式中一種主要依賴於是否視窗的內容有任何佈局檢視能夠滾動他們的內容。如果有這樣的一個檢視,這個視窗將調整大小,這樣的假設可以使滾動視窗的內容在一個較小的區域中可見的。這個是主視窗預設的行為設定。
    也就是說,系統自動決定是採用平移模式還是壓縮模式,決定因素在於內容是否可以滾動。

    三、偵聽軟鍵盤的顯示隱藏
    有時候,藉助系統本身的機制來實現主視窗的調整並非我們想要的結果,我們可能希望在軟鍵盤顯示隱藏的時候,手動的對佈局進行修改,以便使軟鍵盤彈出時更加美觀。這時就需要對軟鍵盤的顯示隱藏進行偵聽。
    直接對軟鍵盤的顯示隱藏偵聽的方法本人沒有找到,如果哪位找到的方法請務必告訴本人一聲。還有本方法針對壓縮模式,平移模式不一定有效。
    我們可以藉助軟鍵盤顯示和隱藏時,對主視窗進行了重新佈局這個特性來進行偵聽。如果我們設定的模式為壓縮模式,那麼我們可以對佈局的onSizeChanged函式進行跟蹤,如果為平移模式,那麼該函式可能不會被呼叫。
    我們可以重寫根佈局,因為根佈局的高度一般情況下是不發生變化的。
    假設跟佈局為線性佈局,模式為壓縮模式,我們寫一個例子,當輸入法彈出時隱藏某個view,輸入法隱藏時顯示某個view。

  1. publicclass ResizeLayout extends LinearLayout{  
  2.     private OnResizeListener mListener; 
  3.     publicinterface OnResizeListener { 
  4.         void OnResize(int w, int h, int oldw, int oldh); 
  5.     } 
  6.     publicvoid setOnResizeListener(OnResizeListener l) { 
  7.         mListener = l; 
  8.     } 
  9.     public ResizeLayout(Context context, AttributeSet attrs) { 
  10.         super(context, attrs); 
  11.     } 
  12.     @Override
  13.     protectedvoid onSizeChanged(int w, int h, int oldw, int oldh) {     
  14.         super.onSizeChanged(w, h, oldw, oldh); 
  15.         if (mListener != null) { 
  16.             mListener.OnResize(w, h, oldw, oldh); 
  17.         } 
  18.     } 

    在我們的Activity中,通過如下方法呼叫:

  1. publicclass InputMethodTestActivity extends Activity { 
  2.     privatestaticfinalint BIGGER = 1
  3.     privatestaticfinalint SMALLER = 2
  4.     privatestaticfinalint MSG_RESIZE = 1
  5.     privatestaticfinalint HEIGHT_THREADHOLD = 30
  6.     class InputHandler extends Handler { 
  7.         @Override
  8.         publicvoid handleMessage(Message msg) { 
  9.             switch (msg.what) { 
  10.             case MSG_RESIZE: { 
  11.                 if (msg.arg1 == BIGGER) { 
  12.                     findViewById(R.id.bottom_layout).setVisibility(View.VISIBLE); 
  13.                 } else { 
  14.                     findViewById(R.id.bottom_layout).setVisibility(View.GONE); 
  15.                 } 
  16.             } 
  17.                 break
  18.             default
  19.                 break
  20.             } 
  21.             super.handleMessage(msg); 
  22.         } 
  23.     } 
  24.     private InputHandler mHandler = new InputHandler(); 
  25.     /** Called when the activity is first created. */
  26.     @Override
  27.     publicvoid onCreate(Bundle savedInstanceState) { 
  28.         super.onCreate(savedInstanceState); 
  29.         setContentView(R.layout.main); 
  30.         ResizeLayout layout = (ResizeLayout) findViewById(R.id.root_layout); 
  31.         layout.setOnResizeListener(new ResizeLayout.OnResizeListener() { 
  32.             publicvoid OnResize(int w, int h, int oldw, int oldh) { 
  33.                 int change = BIGGER; 
  34.                 if (h < oldh) { 
  35.                     change = SMALLER; 
  36.                 } 
  37.                 Message msg = new Message(); 
  38.                 msg.what = 1
  39.                 msg.arg1 = change; 
  40.                 mHandler.sendMessage(msg); 
  41.             } 
  42.         }); 
  43.     } 

    這裡特別需要注意的是,不能直接在OnResizeListener中對要改變的View進行更改,因為OnSizeChanged函式實際上是執行在View的layout方法中,如果直接在onSizeChange中改變view的顯示屬性,那麼很可能需要重新呼叫layout方法才能顯示正確。然而我們的方法又是在layout中呼叫的,因此會出現錯誤。因此我們在例子中採用了Handler的方法。