Android軟鍵盤的全面解析,讓你不再怕控件被遮蓋

本文轉載自:https://blog.csdn.net/l540675759/article/details/74528641

背景

1.Android軟鍵盤這塊從我入職到現在,是一個一直糾纏我的問題。

2.從佈局擠壓,到EditText顯示不全,在到彈出時卡頓,在Android軟鍵盤面前我無數次跌倒。

3.因爲網上大多數的知識點比較分散而且很雜,所以本篇做一個整合篇。

4.Android軟鍵盤這塊知識點比較密集,瞭解過一次之後,差不多什麼情況都可以找到原因了。

5.感謝Android軟鍵盤的問題,從我入職陪伴我到現在,讓我一個一個不停的解決。


前言

本文將從以下幾個方面進行介紹:

(1)InputMethodService的源碼解析,從源碼解讀中告訴你爲什麼軟鍵盤彈出的是一個Dialog

(2)Android軟鍵盤顯示時,設置windowSoftInputMode的作用

(3)EditText設置imeOptions屬性對軟鍵盤的影響

(4)軟鍵盤上面的按鍵監聽

(5)橫屏狀態下,不希望軟鍵盤顯示全屏怎麼處理

(6)控制軟鍵盤的彈出和關閉的方法

(7)EditText在軟鍵盤彈出的時候顯示不全,怎麼獲取軟鍵盤彈出和關閉的監聽

(8)軟鍵盤彈出的時候,造成頁面卡頓,這時候如何發現問題並解決問題

Android軟鍵盤的顯示原理

軟鍵盤其實是一個Dialog

    InputMethodService爲我們的輸入法創建了一個Dialog,並且對某些參數進行了設置,使之能夠在底部或者全屏顯示。當我們點擊輸入框時,系統會對當前的主窗口進行調整,以便留出相應的空間來顯示該Dialog在底部,或者全屏。

    其實這段話我們經常在各種軟鍵盤博客所看到,但是大家並不知道Android是怎麼爲我們創建的這個Dialog,所以我先帶大家來看下軟鍵盤生成這塊的源碼,瞭解軟鍵盤的生成流程。


InputMethodService的源碼解析

我們先來看一下InputMethodService的繼承關係:

InputMethodService的繼承關係

因爲InputMethodService屬於服務,接下來我們先看一下服務的入口onCreate()方法:

    @Override 
    public void onCreate() {
        //設置主題與xml裏面設置theme是一樣的道理
        mTheme = Resources.selectSystemTheme(mTheme,
                getApplicationInfo().targetSdkVersion,
                android.R.style.Theme_InputMethod,
                android.R.style.Theme_Holo_InputMethod,
                android.R.style.Theme_DeviceDefault_InputMethod,
                android.R.style.Theme_DeviceDefault_InputMethod);
        super.setTheme(mTheme);
        //創建InputMethodMananger
        mImm = (InputMethodManager)getSystemService(INPUT_METHOD_SERVICE);
        mSettingsObserver = SettingsObserver.createAndRegister(this);
        mShouldClearInsetOfPreviousIme = (mImm.getInputMethodWindowVisibleHeight() > 0);
        mInflater = (LayoutInflater)getSystemService(
                Context.LAYOUT_INFLATER_SERVICE);
        /**
         * 這裏注意一下,首先這裏的命名屬於Window,然後我們發現了Gravity.BOTTOM,就更加確定了這個就是
         * 軟鍵盤所創建的Dialog對象
         */
        mWindow = new SoftInputWindow(this"InputMethod", mTheme, nullnull, mDispatcherState,
                WindowManager.LayoutParams.TYPE_INPUT_METHOD, Gravity.BOTTOM, false);
        if (mHardwareAccelerated) {
            mWindow.getWindow().addFlags(WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
        }
        initViews();
        mWindow.getWindow().setLayout(MATCH_PARENT, WRAP_CONTENT);
    }

通過上面的分析,我們懷疑這裏的SoftInputWindow是軟鍵盤彈出創建的Dialog對象,下面我們看下SoftInputWindow的源碼。

public class SoftInputWindow extends Dialog{
    ....
}

    stateUnspecified-不指定軟鍵盤的狀態(隱藏還是可見) 將由系統選擇合適的狀態,或依賴主題中的設置,這是對軟鍵盤行爲的默認設置

    stateUnchanged-保留狀態 當 Activity 轉至前臺時保留軟鍵盤最後所處的任何狀態,無論是可見還是隱藏

    stateHidden-隱藏軟鍵盤 當用戶確實是向前導航到 Activity,而不是因離開另一Activity 而返回時隱藏軟鍵盤

    stateAlwaysHidden-始終隱藏軟鍵盤 當 Activity 的主窗口有輸入焦點時始終隱藏軟鍵盤

    stateVisible-顯示軟鍵盤 在正常的適宜情況下(當用戶向前導航到 Activity 的主窗口時)顯示軟鍵盤

    stateAlwaysVisible-顯示軟鍵盤 當用戶確實是向前導航到 Activity,而不是因離開另一Activity 而返回時.

    (2)在軟鍵盤彈出時,是否需要Activity對此進行調整
    

      還是同樣的操作,點擊最下面的EditText13

      (1)設置windowSoftInputMode爲adjustNoting

      這裏寫圖片描述

      我們可以看出,當點擊EditText12時,彈出軟鍵盤將主窗口下半部分給遮蓋,並且主窗口沒有做出任何反應,和不加ScrollView是一樣的情況。

      (2)設置windowSoftInputMode爲adjustResize

      adjustResize

      我們可以發現,當設置其屬性爲adjustResize時,當軟鍵盤彈出時,ScrollView會重新繪製,然後滾動EditText13位置,使其顯示在軟鍵盤之上。

      (3)設置windowSoftInputMode爲adjustUnspecified

      adjustResize

      當設置其屬性爲默認屬性adjustUnspecified時,可以發現在添加了ScrollView控件時,佈局的窗口並不會上移(這個觀察Toolbar就可以發現),而通過重繪ScrollView,讓其滾動到最低端,並且給軟鍵盤流出控件,而這個表現即和adjustResize完全一致。

      (4)設置windowSoftInputMode爲adjustPan

      adjustPan

      可以發現,在滑動空間下,設置屬性adjustPan時,依舊會將主窗口上移,來使EditText13顯示在軟鍵盤之上,可以通過觀察Toolbar得知。

      windowSoftInputMode總結

      通過上面的例子,我們可以完全理解adjust系列的各個參數的作用。而軟鍵盤的顯示和隱藏這裏面需要並不多,而且內容並不算複雜,大家回去自己嘗試下就可以。

      adjust系列的參數總結圖


      EditText設置imeOptions屬性對軟鍵盤的影響

      在日常開發中,如果需要將軟鍵盤的Enter鍵更改爲其他鍵,可以設置其android:imeOptions 屬性,這個屬性可以控制軟鍵盤的Enter鍵,以及橫屏情況下的軟鍵盤顯示狀態。

      該設置必須是下面所列的值之一,或者是一個“action…”值加上一個“flag…”值的組合,在action…組中設置多個值(例如,多個“action…”值)都會產生未定義結果,而flag….可以設置多個。各值之間使用垂直條 (|) 分隔

      (1)控制軟鍵盤上的Enter鍵
      
      • 1

      android:imeOptions=”normal”

      當android:singleLine=”true”
      輸入框後面還有輸入控件的時候會顯示next,沒有時會顯示done(完成)
      當android:singleLine=”false”
      輸入框會進行換行操作

      Next

      android:imeOptions=”actionUnspecified”

      該屬性爲默認屬性,一般情況下爲“normal”的使用情形。

      Next

      android:imeOptions=”actionNone”

      顯示回車鍵,當singleLine爲true的時候,會跳到下個可輸出的控件,否則軟鍵盤消失,輸入完畢。

      回車鍵

      android:imeOptions=”actionGo”

      顯示爲Go(前往)按鈕,需要配合android:singleLine使用,否則爲回車鍵起換行作用,並且需要自己寫事件。

      前往

      android:imeOptions=”actionSearch”

      顯示搜索(Search)按鈕,需要配合android:singleLine使用,否則爲回車鍵起換行作用,並且需要自己寫事件。

      搜索

      android:imeOptions=”actionSend”

      顯示send(發送)按鈕,需要配合android:singleLine使用,否則爲回車鍵起換行作用,並且需要自己寫事件。

      發送

      android:imeOptions=”actionNext”

      顯示next(下一步)按鈕,作用是跳到下一個可輸入的控件,需要配合android:singleLine使用,否則爲回車鍵起換行作用。

      Next

      android:imeOptions=”actionDone”

      顯示done(完成)按鈕,作用編輯完成,讓軟鍵盤消失.需要配合android:singleLine使用,否則爲回車鍵起換行作用。

      回車鍵

      android:imeOptions=”actionPrevious”

      顯示上一步按鈕,如果前面有輸入控件,點擊後會回到前一個控件獲取焦點,.需要配合android:singleLine使用,否則爲回車鍵起換行作用。

      回車鍵

      可能各個輸入法的顯示圖標不一樣,但是效果是一樣的,這裏用的是搜狗輸入法。

      (2)橫屏下控制軟鍵盤
      

        而如果對於這一塊有什麼不明白的,可以參考這篇博客。

        Android手動顯示和隱藏軟鍵盤方法總結

        實際案例3:EditText顯示不全,並且需要監聽軟鍵盤彈出和關閉

        實際情況

        現在有一個常見的需求,EditText被佈局包裹,然後需要將原生的EditText背景給替換掉或者直接設置爲null(或者其他),然後和佈局上下存在間距,然後整體與底部對齊。

        這時候彈出軟鍵盤,請看圖:

        這裏寫圖片描述

        從圖中可以發現,當原生的EditText背景被替換之後,軟鍵盤會遮蓋掉自定義區域,並且直接顯示在EditText之下,正常情況下,我們是希望軟鍵盤顯示在整個外層的Layout之下的。

        當時對於這個問題,我沒有頭緒好一陣子,現在來看,那時候挺年輕的。

        而這個問題,就屬於軟鍵盤遮擋佈局的問題:

        引用塊內容

        (1)軟鍵盤遮蓋焦點:

        當軟鍵盤彈出的時候,將EditText等輸入類的控件的焦點遮蓋時,這時候可以設置adjustPan或者adjustResize可以很好的解決。

        這裏如果理解好本博文第一塊內容就能很好解決這個問題。

        (2)軟鍵盤遮蓋沒有遮蓋焦點,但是遮蓋了需要顯示的控件:

        這時候設置通過設置屬性adjustPan或者adjustResize還不足以解決問題,因爲軟鍵盤並沒有遮蓋了EditText的焦點,所以單獨設置這兩個屬性是對軟鍵盤或者界面是無法產生改變的。

        這時我們必須從另外一個角度來考慮這個問題,就是來監聽軟鍵盤的彈出和關閉來操作佈局,來解決這個問題。

        這時候普遍會有以下的幾種解決方案:

        (1)設置adjustResize屬性,當軟鍵盤彈出的時候會重繪佈局,然後設置根佈局的OnLayoutChangeListener的監聽,來監聽佈局的變化。

         mScrollView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
         @Override
         public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
                        Log.d("new change ""left : " + left + "top : " + top + "right : " + right + "bottom : " + bottom);
                        Log.d("old change ""oldLeft : " + oldLeft + "oldTop : " + oldTop + "oldRight : " + oldRight + "oldBottom : " + oldBottom);
                    }
                });
        
        //當軟鍵盤彈出
        07-09 21:16:22.911 23653-23653/com.xiucai.softdemo D/new change: left : 0top : 0right : 1080bottom : 817
        07-09 21:16:22.911 23653-23653/com.xiucai.softdemo D/old change: oldLeft : 0oldTop : 0oldRight : 1080oldBottom : 1692
        
        //當軟鍵盤關閉
        07-09 21:16:44.457 23653-23653/com.xiucai.softdemo D/new change: left : 0top : 0right : 1080bottom : 1692
        07-09 21:16:44.457 23653-23653/com.xiucai.softdemo D/old change: oldLeft : 0oldTop : 0oldRight : 1080oldBottom : 817

          上面代碼是借鑑android 解決輸入法鍵盤遮蓋佈局問題 這篇文章而來,目的讓大家瞭解這種方式是怎麼判斷軟鍵盤的彈出和隱藏。

          裏面需要平移的View,不一定是ScrollView,其他View也可,一般來說作爲XML的根佈局即可。

          平常一般來說,我會使用這個工具類

          package com.xiucai.common.manager;
          
          import android.graphics.Rect;
          import android.util.Log;
          import android.view.View;
          import android.view.ViewTreeObserver;
          
          import java.util.LinkedList;
          import java.util.List;
          
          /**
           * Created by SuperD on 2017/5/12.
           */
          
          public class SoftKeyBroadManager  implements ViewTreeObserver.OnGlobalLayoutListener{
          
              public interface SoftKeyboardStateListener {
                  void onSoftKeyboardOpened(int keyboardHeightInPx);
          
                  void onSoftKeyboardClosed();
              }
          
              private final List<SoftKeyboardStateListener> listeners = new LinkedList<SoftKeyboardStateListener>();
              private final View activityRootView;
              private int lastSoftKeyboardHeightInPx;
              private boolean isSoftKeyboardOpened;
          
              public SoftKeyBroadManager(View activityRootView) {
                  this(activityRootView, false);
              }
          
              public SoftKeyBroadManager(View activityRootView, boolean isSoftKeyboardOpened) {
                  this.activityRootView = activityRootView;
                  this.isSoftKeyboardOpened = isSoftKeyboardOpened;
                  activityRootView.getViewTreeObserver().addOnGlobalLayoutListener(this);
              }
          
              @Override
              public void onGlobalLayout() {
                  final Rect r = new Rect();
                  //r will be populated with the coordinates of your view that area still visible.
                  activityRootView.getWindowVisibleDisplayFrame(r);
          
                  final int heightDiff = activityRootView.getRootView().getHeight() - (r.bottom - r.top);
                  Log.d("SoftKeyboardStateHelper""heightDiff:" + heightDiff);
                  if (!isSoftKeyboardOpened && heightDiff > 500) { // if more than 100 pixels, its probably a keyboard...
                      isSoftKeyboardOpened = true;
                      notifyOnSoftKeyboardOpened(heightDiff);
                      //if (isSoftKeyboardOpened && heightDiff < 100)
                  } else if (isSoftKeyboardOpened && heightDiff < 500) {
                      isSoftKeyboardOpened = false;
                      notifyOnSoftKeyboardClosed();
                  }
              }
          
              public void setIsSoftKeyboardOpened(boolean isSoftKeyboardOpened) {
                  this.isSoftKeyboardOpened = isSoftKeyboardOpened;
              }
          
              public boolean isSoftKeyboardOpened() {
                  return isSoftKeyboardOpened;
              }
          
              /**
               * Default value is zero (0)
               *
               * @return last saved keyboard height in px
               */
              public int getLastSoftKeyboardHeightInPx() {
                  return lastSoftKeyboardHeightInPx;
              }
          
              public void addSoftKeyboardStateListener(SoftKeyboardStateListener listener) {
                  listeners.add(listener);
              }
          
              public void removeSoftKeyboardStateListener(SoftKeyboardStateListener listener) {
                  listeners.remove(listener);
              }
          
              private void notifyOnSoftKeyboardOpened(int keyboardHeightInPx) {
                  this.lastSoftKeyboardHeightInPx = keyboardHeightInPx;
          
                  for (SoftKeyboardStateListener listener : listeners) {
                      if (listener != null) {
                          listener.onSoftKeyboardOpened(keyboardHeightInPx);
                      }
                  }
              }
          
              private void notifyOnSoftKeyboardClosed() {
                  for (SoftKeyboardStateListener listener : listeners) {
                      if (listener != null) {
                          listener.onSoftKeyboardClosed();
                      }
                  }
              }
          }
          

            最後如果感覺上面的方案不可行,Github也有一個現成方案,但是博主本人沒試過,原理都是一樣的。大家可以自行取捨。

            Github軟鍵盤監聽的工具類

            知乎上討論軟鍵盤的文章


            實際案例4: 軟鍵盤彈出來卡頓,找不到原因?

            正常來說博文實差不多到這裏就應該結束了,但是博主在實際開發中,也會遇到一些詭異的現象,例如軟鍵盤彈出卡頓,但是這種情況下,根本無法定位到卡頓原因。

            博主遇到這個問題時,懷疑了設置屬性錯誤,懷疑了線程XX沒關,懷疑了佈局太過於複雜,總之該想的博主都想了,但是無論怎麼試都是徒勞的。

            因爲博主犯了一個大錯

            在沒找到原因之前,胡亂猜測,可能是這塊?是不是那個的問題,而不確定問題的來源這個問題我感覺大家都會遇到,不從事情的本質上下手,這樣會多花很多時間用在無用的地方,使自己的開發效率很低。

            推薦一款檢測卡頓的神器BlockCanary

            還原下我當時遇到的問題:

            當時我在做一個直播間的功能,直播間從主頁跳轉進入的,給大家截一張圖,大家就懂了。

            項目主頁

            當時做直播間其他功能的時,發現直播間軟鍵盤在彈出和關閉的時候卡頓。

            當時懷疑:

            (1)什麼動畫沒停,什麼線程沒關。

            (2)軟鍵盤 彈出的時候是不是加載的佈局太多.

            (3)直播間的佈局太過於複雜,導致軟鍵盤彈出時繪製卡頓。

            在長期的測試發現一個現象,就是在高端機型上這種狀態不明顯,而在底端機型問題比較嚴重,有時候彈出軟鍵盤卡頓很長時間。

            過了半個月博主思路換了,想想軟鍵盤彈出卡頓,能不能從卡頓原因下手,來解決問題,後來找到了BlockCanary,接入使用後發現:

            原因:
            竟然是 主頁在軟鍵盤每次彈出或者關閉的時候重新繪製.因爲當時BlockCanary當時指向主頁的RecyclerView重繪,我當時想是不可能的.

              最後,博主憑直覺認爲和主頁SingleTask有關,因爲沒有確切的理由,這裏只是提出自己的觀點,而最後問題也解決了。

              如果沒有BlockCanary我永遠發現不了,卡頓的原因是在看不見的主頁。

              後來我給主頁設置成adjustNothing因爲主頁不需要彈出軟鍵盤。


              實際案例5:Android鍵盤面板衝突,佈局閃動的解決方法

              這個問題是我在找Android軟鍵盤相關的問題的時候發現的,這塊我也沒遇到這個問題,所以給大家兩個相關的介紹的地址,希望能對大家有幫助。

              解決衝突

              沒有解決衝突

              解決Android軟鍵盤,佈局閃動的相關博文

              解決Android軟鍵盤佈局閃動的Demo

              開源的Android軟鍵盤佈局閃動的解決方案


              總結

              (1)第一次寫這麼長的博客,感覺會有一些不足,各位看官如果有不合理的地方,或者有誤的地方請直接指出。

              (2)本來想整理成一個Demo的,後來簡單看來下,該有的幾乎都貼出來了,有需要的可以按需複製就可以。

              (3)寫完這篇博客之後,感覺博客乾貨還是不多,所以定位這篇文章算是總結性質加上實際案例性質的博客。

              (4)Android軟鍵盤的總結就差不多到這裏,希望各位看官,如果看到這裏有收穫,就點點贊,灌灌水,頂一波,這樣博主纔有寫下去的動力。

              (5)感謝小輝同學的校驗,調整了文章中不通順的地方。


              參考文檔

            1.徹底搞定Android開發中軟鍵盤的常見問題
            http://blog.csdn.net/mynameishuangshuai/article/details/51567357
            2.Android UI(EditText)詳解
            http://blog.csdn.net/qq_28057577/article/details/51919965?locationNum=12&fps=1
            3.微信軟鍵盤佈局閃動問題
            https://blog.dreamtobe.cn/2015/09/01/keyboard-panel-switch/
            
              發表評論
              所有評論
              還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
              相關文章