Android軟鍵盤遮擋問題解決
在開發登入介面時,在點選某個EditText準備輸入時,彈出的軟鍵盤遮擋了按鈕或者下面的輸入框,在完成這個文字框的輸入後,想要繼續下面的操作,需要先隱藏軟鍵盤。這會影響使用者操作的流暢感,所以需要解決。在嘗試了網上的幾種處理方法後,最終選擇了一種比較滿意的方式。
下面先給一個圖,來講解下問題所在:
這個圖中有四個場景:
第一個場景,是沒有彈出軟鍵盤的登入介面,可以看出所有view都正常展示出來了;
第二個場景,彈出了軟鍵盤,此時,下面的密碼輸入框、登入按鈕都被遮擋了;
第三個場景,彈出了軟鍵盤,輸入了一個字母,此時,軟鍵盤又增長了一部分,但是使用者名稱輸入框也上移了,並沒有被遮蓋。不同的手機的軟鍵盤不同,我是在華為手機上遇到這種情況,在解決遮擋問題時也要考慮這個場景;
第四個場景,是我們的目標,彈出了軟鍵盤,原來的檢視整體上移了,顯示出來我們要操作的view。
下面說下解決軟鍵盤遮蓋問題的過程:
首先,是希望Android系統有自帶的屬性,一設定就搞定問題的。
搜尋了下,都是說使用WindowSoftInputMode屬性,設定為”adjustPan|stateHidden”或者”adjustResize|stateHidden”,經過測試,這種設定對我的佈局沒有效果,基本與不設定的預設效果相同。軟鍵盤正好在編輯的EditText的下面,會遮擋登入按鈕。
第二種方法,是使用ScrollView:
在整體佈局的外面加一個ScrollView,在文字框的onTouch監聽動作中,滾動整個檢視。
實測問題:
1,監聽到onTouch動作後,進行滾動整個檢視操作,需要延時才有效。
原因:onTouch時,軟鍵盤還沒有彈出,此時滾動到底部與不滾動是一樣的效果。需要等軟鍵盤彈出後,介面中的內容超過了一螢幕,此時的滾動才有效。
2,這個延時時間是多少?
不同手機的延時時間不一致,好的手機100ms就可以了,慢的手機延時500ms也不一定每次都有效果。
3,延時帶來的問題:
若延時超過300ms,人眼就能感覺出來了:先是軟鍵盤彈出,然後才是檢視滾動。使用者會感覺有些奇怪:為什麼要動作兩次呢?
這種方案,可以湊合使用了,可是,我們要做優雅的開發者,繼續尋找!
第三種方法:監聽佈局變化;
既然使用延時不是一個優雅的方案,那麼,滾動檢視的最好的時間,當然是在佈局變化的時刻。
這裡,可以使用OnGlobalLayoutListener來實現。我們來學習下:
OnGlobalLayoutListener 是ViewTreeObserver的內部類,當一個檢視樹的佈局發生改變時,可以被ViewTreeObserver監聽到,這是一個註冊監聽檢視樹的觀察者(observer),在檢視樹的全域性事件改變時得到通知。
總結為簡單一句話:使用OnGlobalLayoutListener可以監聽到佈局的變化。
監聽到佈局變化後,我們就可以自由的操作了:
可以外包一個ScrollView來進行滾動,使用一個
scrollView.fullScroll(ScrollView.FOCUS_DOWN)
來滾動到底部。
不過,不使用ScrollView也能進行滾動的,例如LinearLayout也是可以滾動的,你還不知道吧,其實,我也是才知道:最基礎的View就有個ScrollTo()函式的。
好了,基礎知識準備好了,剩下就是滾動的距離計算了,我們這樣來計算:
通過窗體的根View求出總的區域和可視區域,這樣就可以計算出被遮擋的區域的高度,如果超過一定的值就判斷為軟鍵盤彈出了,然後將根View ScrollTo到一個位置,將被遮擋的View展示出來。
這裡還有個要注意的地方,就是ScrollTo的引數,先看看函式原型:
public void scrollTo(int x, int y)
兩個引數x、y,是要滾動到位置的座標,注意,它們是絕對座標。
而我們計算滾動距離的時候,是計算的相對滾動距離。還記得上面的場景2與場景3麼,點選輸入框,滾動檢視,進入場景2,然後點選一個字母,進入場景3,此時,就是一個比較小的相對滾動距離。後面,我們在程式碼中也有相應註釋,要注意理解下哦。
下面是貼程式碼的時間了,先展示我的佈局xml:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/ll_loginView"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="#FAFAFA"
android:orientation="vertical" >
<ImageView
android:layout_width="fill_parent"
android:layout_height="350dp"
android:background="#8fE095"
android:scaleType="centerInside"
android:src="@drawable/logo" />
<View
android:layout_width="match_parent"
android:layout_height="5dp"
android:focusable="true"
android:focusableInTouchMode="true" />
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="300dp"
android:orientation="vertical" >
<EditText
android:id="@+id/userName"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:hint="請輸入使用者名稱"
android:inputType="text"
android:maxLength="18"
android:paddingBottom="10dip"
android:singleLine="true"
android:textColor="#808080"
android:text=""
android:textSize="18sp" />
<EditText
android:id="@+id/userPwd"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:hint="請輸入密碼"
android:inputType="numberPassword"
android:maxLength="20"
android:paddingBottom="10dip"
android:singleLine="true"
android:textColor="#808080"
android:text=""
android:textSize="18sp" >
</EditText>
<LinearLayout
android:id="@+id/layout03"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:orientation="horizontal"
>
<CheckBox
android:id="@+id/login_check"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checked="true"
/>
<TextView
android:id="@+id/rememberPwd"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="記住使用者名稱"
android:textColor="#808080"
android:textSize="15sp" />
</LinearLayout>
<Button
android:id="@+id/btn_login"
android:layout_width="300dp"
android:layout_height="50dp"
android:layout_gravity="center_horizontal"
android:layout_marginTop="40dp"
android:background="@drawable/login_buton"
android:gravity="center"
android:text="立 即 登 錄"
android:textColor="@android:color/background_light" />
</LinearLayout>
</LinearLayout>
然後是主Activity:
package com.example.loginTest;
import android.app.Activity;
import android.graphics.Rect;
import android.os.Bundle;
import android.view.View;
import android.view.ViewTreeObserver;
import android.view.Window;
import android.widget.Button;
import android.widget.LinearLayout;
import com.example.loginTest.R;
public class MainActivity extends Activity {
private Button btn_login;
private LinearLayout ll_loginView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_main);
btn_login = (Button) findViewById(R.id.btn_login);
ll_loginView = (LinearLayout) findViewById(R.id.ll_loginView );
autoScrollView(ll_loginView, btn_login);//彈出軟鍵盤時滾動檢視
}
/**
* @param root 最外層的View
* @param scrollToView 不想被遮擋的View,會移動到這個Veiw的可見位置
*/
private int scrollToPosition=0;
private void autoScrollView(final View root, final View scrollToView) {
root.getViewTreeObserver().addOnGlobalLayoutListener(
new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
Rect rect = new Rect();
//獲取root在窗體的可視區域
root.getWindowVisibleDisplayFrame(rect);
//獲取root在窗體的不可視區域高度(被遮擋的高度)
int rootInvisibleHeight = root.getRootView().getHeight() - rect.bottom;
//若不可視區域高度大於150,則鍵盤顯示
if (rootInvisibleHeight > 150) {
//獲取scrollToView在窗體的座標,location[0]為x座標,location[1]為y座標
int[] location = new int[2];
scrollToView.getLocationInWindow(location);
//計算root滾動高度,使scrollToView在可見區域的底部
int scrollHeight = (location[1] + scrollToView.getHeight()) - rect.bottom;
//注意,scrollHeight是一個相對移動距離,而scrollToPosition是一個絕對移動距離
scrollToPosition += scrollHeight;
} else {
//鍵盤隱藏
scrollToPosition = 0;
}
root.scrollTo(0, scrollToPosition);
}
});
}
}