Android系統沒有提供鍵盤的彈出/隱藏的API,開發者需自己實現。
網上有各種版本,下面是比較簡單的一種實現(引自 https://www.jianshu.com/p/4575e65f4e19 ):
public class EPSoftKeyBoardListener {
private View rootView;
private int rootViewVisibleHeight;
private OnSoftKeyBoardChangeListener onSoftKeyBoardChangeListener;
public EPSoftKeyBoardListener(Activity activity) {
rootView = activity.getWindow().getDecorView();
rootView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
Rect r = new Rect();
rootView.getWindowVisibleDisplayFrame(r);
int visibleHeight = r.height();
if (rootViewVisibleHeight == 0) {
rootViewVisibleHeight = visibleHeight;
return;
}
if (rootViewVisibleHeight == visibleHeight) {
return;
}
if (rootViewVisibleHeight - visibleHeight > 200) {
if (onSoftKeyBoardChangeListener != null) {
onSoftKeyBoardChangeListener.keyBoardShow(rootViewVisibleHeight - visibleHeight);
}
rootViewVisibleHeight = visibleHeight;
return;
}
if (visibleHeight - rootViewVisibleHeight > 200) {
if (onSoftKeyBoardChangeListener != null) {
onSoftKeyBoardChangeListener.keyBoardHide(visibleHeight - rootViewVisibleHeight);
}
rootViewVisibleHeight = visibleHeight;
return;
}
}
});
}
private void setOnSoftKeyBoardChangeListener(OnSoftKeyBoardChangeListener onSoftKeyBoardChangeListener) {
this.onSoftKeyBoardChangeListener = onSoftKeyBoardChangeListener;
}
public interface OnSoftKeyBoardChangeListener {
void keyBoardShow(int height);
void keyBoardHide(int height);
}
public static void setListener(Activity activity, OnSoftKeyBoardChangeListener onSoftKeyBoardChangeListener) {
EPSoftKeyBoardListener softKeyBoardListener = new EPSoftKeyBoardListener(activity);
softKeyBoardListener.setOnSoftKeyBoardChangeListener(onSoftKeyBoardChangeListener);
}
}
調用方法:
EPSoftKeyBoardListener.setListener(activity, new EPSoftKeyBoardListener.OnSoftKeyBoardChangeListener() {
@Override
public void keyBoardShow(int height) {
Log.d("TAG", "show");
}
@Override
public void keyBoardHide(int height) {
Log.d("TAG", "hide");
}
});
}
上面的實現中,基本思想就是利用 OnGlobalLayoutListener 以及 rootView.getWindowVisibleDisplayFrame 監聽Activity的可視高度來判斷鍵盤的彈出和隱藏。
不過該實現還是不夠簡潔,舉例幾點:
1、幾個if塊其實是互斥條件,一些條件判斷可以省略
3、無端的空行(代碼風格)
2、如果只關注鍵盤的彈出和隱藏,其實不需要給回調函數傳可視變化的高度
4、外部類EPSoftKeyBoardListener, 內部類OnSoftKeyBoardChangeListener,Listener套Listener, 乍一看讓人理不清頭緒
整理一下,簡化實現如下:
public class SoftKeyBoardHelper {
public interface OnSoftKeyboardChangeListener {
void keyBoardShow();
void keyBoardHide();
}
private static class HeightWrapper {
int height;
}
public static void setListener(Activity activity, final OnSoftKeyboardChangeListener listener) {
final View rootView = activity.getWindow().getDecorView();
final HeightWrapper wrapper = new HeightWrapper();
rootView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
Rect r = new Rect();
rootView.getWindowVisibleDisplayFrame(r);
int height = r.height();
if (wrapper.height == 0) {
wrapper.height = height;
} else {
int diff = wrapper.height - height;
if (diff > 200) {
if (listener != null) {
listener.keyBoardShow();
}
wrapper.height = height;
} else if (diff < -200) {
if (listener != null) {
listener.keyBoardHide();
}
wrapper.height = height;
}
}
}
});
}
}
相對比前面的實現,最大的改變是外部類不再有成員變量,從而可以成爲一個存粹工具類(平常的Util, Helper等),
不過有一點代價就是需要用一個對象來包裝height。
由於height不再是類的成員變量,而僅僅是方法中的局部變量,需要加final修飾才能對匿名內部類(OnGlobalLayoutListener)可見,
而一但加上final變只能訪問而不能二次賦值(所以需要用對象包裝起來,通過訪問對象的變量來實現賦值)。
如果用Kotlin, 還可以再簡潔一些:
fun Activity.observeKeyboardChange(onChange: (isShowing: Boolean) -> Unit) {
val rootView = this.window.decorView
val r = Rect()
var lastHeight = 0
rootView.viewTreeObserver.addOnGlobalLayoutListener {
rootView.getWindowVisibleDisplayFrame(r)
val height = r.height()
if (lastHeight == 0) {
lastHeight = height
} else {
val diff = lastHeight - height
if (diff > 200) {
onChange(true)
lastHeight = height
} else if (diff < -200) {
onChange(false)
lastHeight = height
}
}
}
}
調用方法也很簡單:
activity.observeKeyboardChange { isShowing ->
Log.e("MyTag", if (isShowing) "show" else "hide")
}
相對於Java版本的實現,主要是利用Kotlin的幾個語法糖:
1、擴展函數:對於需要用自身作爲參數的方法都可以嘗試用擴展函數來寫(如上面的activity
)
2、高階函數:可以省略一些Listener類的定義 (onChange
)
3、Lambda:只有一個方法的接口,Lambda寫法中可以省略該接口 (onGlobalLayout
)
4、閉包:內部類可以對類外的局部變量訪問和賦值了(lastHeight
)
通過代碼的優化和語法糖的加持,代碼行數從第一版的約60行,簡化爲最終版的約20行。
最後提一點題外話:
Kotlin的語法糖確實甜,不過喫多了也容易“胖”。
一般情況下,Kotlint實現同樣的功能要比Java看上代碼更少,但是編譯出來體積和方法數都比Java多。
筆者用Kotlin改寫了幾個自己的庫,體積都是上漲大約50%左右;
OkHttp庫用Kotlin改寫之後jar包體積相對之前也是漲約50%。
不過也不能因噎廢食,其實很多事情都是求一個均衡,用一定代價獲取編碼的效率,也是值得的。
例如在執行效率方面,Python相對於C/C++很多情況下是要慢一些的,但不妨礙大家對Python的熱愛,正所謂“人生苦短,我用Python"。
同樣也不應看到Kotlin“真香”而棄Java如敝履,無論執行效率還是編寫效率,Java都是很不錯的。