本文轉載自: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屬於服務,接下來我們先看一下服務的入口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, null, null, 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時,當軟鍵盤彈出時,ScrollView會重新繪製,然後滾動EditText13位置,使其顯示在軟鍵盤之上。
(3)設置windowSoftInputMode爲adjustUnspecified
當設置其屬性爲默認屬性adjustUnspecified時,可以發現在添加了ScrollView控件時,佈局的窗口並不會上移(這個觀察Toolbar就可以發現),而通過重繪ScrollView,讓其滾動到最低端,並且給軟鍵盤流出控件,而這個表現即和adjustResize完全一致。
(4)設置windowSoftInputMode爲adjustPan
可以發現,在滑動空間下,設置屬性adjustPan時,依舊會將主窗口上移,來使EditText13顯示在軟鍵盤之上,可以通過觀察Toolbar得知。
windowSoftInputMode總結
通過上面的例子,我們可以完全理解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”
輸入框會進行換行操作
android:imeOptions=”actionUnspecified”
該屬性爲默認屬性,一般情況下爲“normal”的使用情形。
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使用,否則爲回車鍵起換行作用。
android:imeOptions=”actionDone”
顯示done(完成)按鈕,作用編輯完成,讓軟鍵盤消失.需要配合android:singleLine使用,否則爲回車鍵起換行作用。
android:imeOptions=”actionPrevious”
顯示上一步按鈕,如果前面有輸入控件,點擊後會回到前一個控件獲取焦點,.需要配合android:singleLine使用,否則爲回車鍵起換行作用。
可能各個輸入法的顯示圖標不一樣,但是效果是一樣的,這裏用的是搜狗輸入法。
(2)橫屏下控制軟鍵盤
而如果對於這一塊有什麼不明白的,可以參考這篇博客。
實際案例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也有一個現成方案,但是博主本人沒試過,原理都是一樣的。大家可以自行取捨。
實際案例4: 軟鍵盤彈出來卡頓,找不到原因?
正常來說博文實差不多到這裏就應該結束了,但是博主在實際開發中,也會遇到一些詭異的現象,例如軟鍵盤彈出卡頓,但是這種情況下,根本無法定位到卡頓原因。
博主遇到這個問題時,懷疑了設置屬性錯誤,懷疑了線程XX沒關,懷疑了佈局太過於複雜,總之該想的博主都想了,但是無論怎麼試都是徒勞的。
因爲博主犯了一個大錯
在沒找到原因之前,胡亂猜測,可能是這塊?是不是那個的問題,而不確定問題的來源這個問題我感覺大家都會遇到,不從事情的本質上下手,這樣會多花很多時間用在無用的地方,使自己的開發效率很低。
還原下我當時遇到的問題:
當時我在做一個直播間的功能,直播間從主頁跳轉進入的,給大家截一張圖,大家就懂了。
當時做直播間其他功能的時,發現直播間軟鍵盤在彈出和關閉的時候卡頓。
當時懷疑:
(1)什麼動畫沒停,什麼線程沒關。
(2)軟鍵盤 彈出的時候是不是加載的佈局太多.
(3)直播間的佈局太過於複雜,導致軟鍵盤彈出時繪製卡頓。
在長期的測試發現一個現象,就是在高端機型上這種狀態不明顯,而在底端機型問題比較嚴重,有時候彈出軟鍵盤卡頓很長時間。
過了半個月博主思路換了,想想軟鍵盤彈出卡頓,能不能從卡頓原因下手,來解決問題,後來找到了BlockCanary,接入使用後發現:
原因:
竟然是 主頁在軟鍵盤每次彈出或者關閉的時候重新繪製.因爲當時BlockCanary當時指向主頁的RecyclerView重繪,我當時想是不可能的.
最後,博主憑直覺認爲和主頁SingleTask有關,因爲沒有確切的理由,這裏只是提出自己的觀點,而最後問題也解決了。
如果沒有BlockCanary我永遠發現不了,卡頓的原因是在看不見的主頁。
後來我給主頁設置成adjustNothing因爲主頁不需要彈出軟鍵盤。
實際案例5:Android鍵盤面板衝突,佈局閃動的解決方法
這個問題是我在找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/