Android監聽鍵盤彈出隱藏的簡化實現

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都是很不錯的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章