android 中關(guān)于鍵盤和焦點(diǎn)的問題,有時(shí)候處理不好,真的讓人抓狂,昨天在實(shí)現(xiàn)需求的時(shí)候被鍵盤和焦點(diǎn)的問題搞得難受,今天把昨天遇到的問題總結(jié)記錄一下。
1. 鍵盤隱藏和遮擋界面
1.1 鍵盤隱藏
在一個(gè)常見的輸入信息的界面(保存聯(lián)系人信息)為例,界面如圖所示。

有時(shí)候我們需要在進(jìn)入界面的時(shí)候隱藏輸入法鍵盤,有兩種方法:
- 在AndroidManifest.xml中對(duì)相應(yīng)的Activity添加一下代碼:
android:windowSoftInputMode="stateHidden"
- 在java代碼中調(diào)用:
getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN);
多說一句:
網(wǎng)上較多的實(shí)現(xiàn)方式是通過InputMethodManager實(shí)現(xiàn):
Timer timer=new Timer();
timer.schedule(new TimerTask() {
public void run() {
InputMethodManager inputMethodManager=(InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
inputMethodManager.toggleSoftInput(0, InputMethodManager.HIDE_NOT_ALWAYS);
}
}, 2000);
但是在我的測(cè)試中,并沒有實(shí)現(xiàn)我的需求,這個(gè)代碼會(huì)在進(jìn)入界面時(shí)彈出鍵盤,然后在隱藏鍵盤。而我的需求是,進(jìn)入界面時(shí)就不能顯示鍵盤,所以不能通過InputMethodManager實(shí)現(xiàn),只能通過windowSoftInputMode實(shí)現(xiàn)。
1.2 鍵盤遮擋
鍵盤隱藏已經(jīng)解決了,再看看,又出現(xiàn)一個(gè)問題。如果界面中內(nèi)容較多,比如上圖中備注1的輸入框,點(diǎn)擊彈起鍵盤時(shí),默認(rèn)情況下,鍵盤會(huì)把界面中的內(nèi)容整體平移推到上面。如圖,連標(biāo)題欄也被擠到屏幕外面去了。

以下內(nèi)容摘自 徹底搞定Android開發(fā)中軟鍵盤的常見問題
Android定義了一個(gè)屬性,名字為windowSoftInputMode, 這個(gè)屬性用于設(shè)置Activity主窗口與軟鍵盤的交互模式,用于避免軟鍵盤遮擋內(nèi)容的問題。
我們可以在AndroidManifet.xml中對(duì)Activity進(jìn)行設(shè)置。如:android:windowSoftInputMode=”stateUnchanged|adjustPan”。
該屬性可選的值有兩部分,一部分為軟鍵盤的狀態(tài)控制,控制軟鍵盤是隱藏還是顯示,另一部分是Activity窗口的調(diào)整,以便騰出空間展示軟鍵盤。
android:windowSoftInputMode的屬性設(shè)置必須是下面中的一個(gè)值,或一個(gè)”state”值加一個(gè)”adjust”值的組合,各個(gè)值之間用 | 分開。
- stateUnspecified-未指定狀態(tài):當(dāng)我們沒有設(shè)置android:windowSoftInputMode屬性的時(shí)候,軟件默認(rèn)采用的就是這種交互方式,系統(tǒng)會(huì)根據(jù)界面采取相應(yīng)的軟鍵盤的顯示模式。
- stateUnchanged-不改變狀態(tài):當(dāng)前界面的軟鍵盤狀態(tài),取決于上一個(gè)界面的軟鍵盤狀態(tài),無論是隱藏還是顯示。
- stateHidden-隱藏狀態(tài):當(dāng)設(shè)置該狀態(tài)時(shí),軟鍵盤總是被隱藏,不管是否有輸入的需求。
- stateAlwaysHidden-總是隱藏狀態(tài):當(dāng)設(shè)置該狀態(tài)時(shí),軟鍵盤總是被隱藏,和stateHidden不同的是,當(dāng)我們跳轉(zhuǎn)到下個(gè)界面,如果下個(gè)頁面的軟鍵盤是顯示的,而我們?cè)俅位貋淼臅r(shí)候,軟鍵盤就會(huì)隱藏起來。
- stateVisible-可見狀態(tài):當(dāng)設(shè)置為這個(gè)狀態(tài)時(shí),軟鍵盤總是可見的,即使在界面上沒有輸入框的情況下也可以強(qiáng)制彈出來出來。
- stateAlwaysVisible-總是顯示狀態(tài):當(dāng)設(shè)置為這個(gè)狀態(tài)時(shí),軟鍵盤總是可見的,和stateVisible不同的是,當(dāng)我們跳轉(zhuǎn)到下個(gè)界面,如果下個(gè)頁面軟鍵盤是隱藏的,而我們?cè)俅位貋淼臅r(shí)候,軟鍵盤就會(huì)顯示出來。
- adjustUnspecified-未指定模式:設(shè)置軟鍵盤與軟件的顯示內(nèi)容之間的顯示關(guān)系。當(dāng)你跟我們沒有設(shè)置這個(gè)值的時(shí)候,這個(gè)選項(xiàng)也是默認(rèn)的設(shè)置模式。在這中情況下,系統(tǒng)會(huì)根據(jù)界面選擇不同的模式。
- adjustResize-調(diào)整模式:該模式下窗口總是調(diào)整屏幕的大小用以保證軟鍵盤的顯示空間;這個(gè)選項(xiàng)不能和adjustPan同時(shí)使用,如果這兩個(gè)屬性都沒有被設(shè)置,系統(tǒng)會(huì)根據(jù)窗口中的布局自動(dòng)選擇其中一個(gè)。
- adjustPan-默認(rèn)模式:該模式下通過不會(huì)調(diào)整來保證軟鍵盤的空間,而是采取了另外一種策略,系統(tǒng)會(huì)通過布局的移動(dòng),來保證用戶要進(jìn)行輸入的輸入框肯定在用戶的視野范圍里面,從而讓用戶可以看到自己輸入的內(nèi)容。
因?yàn)椋谀J(rèn)情況下,系統(tǒng)使用的adjustPan,這種方式平移界面中的內(nèi)容,與之相當(dāng)應(yīng)另一種常用的模式是adjustResize,意為調(diào)整(壓縮)模式,他會(huì)把界面中的空間壓縮,如果有ScrollView,會(huì)讓ScrollView自動(dòng)滾動(dòng)到合適的位置,以完全顯示鍵盤。
由于我的需求不是使用adjustPan模式,所以我直接使用adjustResize模式,AndroidManifest.xml中的聲明如下:
android:windowSoftInputMode="stateHidden|adjustResize"
然后就出現(xiàn)了鍵盤遮擋輸入框的現(xiàn)象,如圖所示,之前可以看到[備注1]和[備注2]被擋住了。

為解決這種情況,需要給頂層布局嵌入一個(gè)ScrollView,然后再來測(cè)試下。
截圖如下所示,可以看到“基本正?!绷?,點(diǎn)擊輸入框會(huì)把空白區(qū)域壓縮,并滑動(dòng)ScrollView,沒有什么大問題,但是只要仔細(xì)觀察,還是可以看出一點(diǎn)不合理的地方,鍵盤頂著輸入法光標(biāo)的下邊緣,但是實(shí)際上ScrollView可以再往下滑動(dòng)一點(diǎn)點(diǎn)的。

1.3 解決輸入框的一點(diǎn)點(diǎn)遮擋的問題
輸入法鍵盤遮擋了[備注1]的EditText下面一點(diǎn)點(diǎn),實(shí)際上鍵盤是在光標(biāo)的正下方緊貼著顯示地,但是此時(shí)的UI交互體驗(yàn)較差,應(yīng)該讓鍵盤彈起時(shí),ScrollView往上多滑動(dòng)一點(diǎn)點(diǎn)。
1.3.1 fullScroll
剛開始使用比較笨的方法,檢測(cè)EditText獲得焦點(diǎn)時(shí),把ScrollView滑動(dòng)到最底端,在網(wǎng)上搜索看到ScrollView滑動(dòng)到最底層的代碼是:
scrollView.fullScroll(View.FOCUS_DOWN);
完整代碼
extra.setOnFocusChangeListener(new View.OnFocusChangeListener() {
@Override
public void onFocusChange(View view, boolean b) {
if (b) {
scrollView.fullScroll(View.FOCUS_DOWN);
}
}
});
執(zhí)行代碼,發(fā)現(xiàn)[備注1]的輸入框無法輸入文字了,來看看fullScroll最終調(diào)用的地方:
public boolean fullScroll(int direction) {
boolean down = direction == View.FOCUS_DOWN;
int height = getHeight();
mTempRect.top = 0;
mTempRect.bottom = height;
if (down) {
int count = getChildCount();
if (count > 0) {
View view = getChildAt(count - 1);
mTempRect.bottom = view.getBottom() + mPaddingBottom;
mTempRect.top = mTempRect.bottom - height;
}
}
return scrollAndFocus(direction, mTempRect.top, mTempRect.bottom);
}
最后會(huì)執(zhí)行scrollAndFocus(direction, mTempRect.top, mTempRect.bottom),
private boolean scrollAndFocus(int direction, int top, int bottom) {
boolean handled = true;
int height = getHeight();
int containerTop = getScrollY();
int containerBottom = containerTop + height;
boolean up = direction == View.FOCUS_UP;
View newFocused = findFocusableViewInBounds(up, top, bottom);
if (newFocused == null) {
newFocused = this;
}
if (top >= containerTop && bottom <= containerBottom) {
handled = false;
} else {
int delta = up ? (top - containerTop) : (bottom - containerBottom);
doScrollY(delta);
}
if (newFocused != findFocus()) newFocused.requestFocus(direction);
return handled;
}
會(huì)把ScrollView當(dāng)前區(qū)域中的可以獲焦的View,請(qǐng)求焦點(diǎn)。導(dǎo)致本來屬于[備注1]的焦點(diǎn)又失去了,最終的現(xiàn)象就是[備注1]里無法輸入內(nèi)容。
后來將fullScroll方法替換成scrollTo或者scrollBy方法,直接讓ScrollView滑動(dòng)一個(gè)較大值,滑動(dòng)到底部。后來又發(fā)現(xiàn)通過監(jiān)聽輸入框焦點(diǎn)變化來調(diào)整ScrollView又不符合要求,在第一次點(diǎn)擊輸入框時(shí),ScrollView可以滑動(dòng)到指定位置,但是此時(shí)將鍵盤收起,再點(diǎn)擊輸入框彈起鍵盤時(shí),因?yàn)榻裹c(diǎn)沒有發(fā)生變化,所以又不能滑動(dòng)到指定位置了,在網(wǎng)絡(luò)上搜索,有一個(gè)方法
activity_main.getViewTreeObserver().addOnGlobalLayoutListener將整個(gè)布局添加鑒定,鍵盤彈起來之后可見區(qū)域的高度會(huì)變小,并認(rèn)為此現(xiàn)象就是鍵盤彈起導(dǎo)致的,然后再去滑動(dòng)ScrollView??雌饋硎强梢粤耍菦]有從根本上解決問題,可見區(qū)域高度變化超過m,就認(rèn)為是鍵盤彈起來了,程序中m寫的是100,但是不能保證所有的輸入法高度都超過100吧?
于是后來放棄了這種監(jiān)聽鍵盤彈起,再通過代碼滑動(dòng)ScrollView的方法。
通過實(shí)驗(yàn),我發(fā)現(xiàn)使用默認(rèn)的EditText竟然不會(huì)出現(xiàn)鍵盤遮擋一部分輸入框的現(xiàn)象,如圖所示。

而兩者的區(qū)別僅僅在于,出問題的EditText設(shè)置了背景:
<EditText
android:id="@+id/extra"
android:layout_width="0dp"
android:layout_height="50dp"
android:layout_weight="1"
android:background="#aaa"
android:hint="備注1"
android:gravity="right|center_vertical" />
<EditText
android:id="@+id/extra"
android:layout_width="0dp"
android:layout_height="50dp"
android:layout_weight="1"
android:hint="備注1"
android:gravity="right|center_vertical" />
通過老王的提示,發(fā)現(xiàn)EditText默認(rèn)使用的背景是:
android:background="@android:drawable/edit_text"
而edit_text.xml中使用的背景是一張.9圖片,名為@drawable/textfield_default,在SDK的目錄下搜索圖片,找到圖片,在AS中打開,可以看到.9圖片設(shè)置的顯示的內(nèi)容區(qū)域上面和下面都有padding。
于是,我仿照著給EditText的上下邊距增加Padding值,如下:
<EditText
android:id="@+id/extra"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="#aaa"
android:paddingTop="10dp"
android:paddingBottom="10dp"
android:hint="備注1"
android:gravity="right|center_vertical" />
注意高度需要設(shè)置為wrap_content,否則需要計(jì)算好高度。
我的理解就是: 鍵盤彈起的時(shí)候上端頂?shù)奈恢糜晒鈽?biāo)的高度和paddingTop和paddingBottom之和決定。也就是

這里理解的不知道對(duì)不對(duì),以后在驗(yàn)證一下。