Android監聽輸入法並獲取高度——輸入法與頁面佈局無縫切換

hzw1490152780934發表於2018-02-10

在QQ或者微信的聊天頁面,當輸入法和表情欄互相切換時,過度非常自然,而且表情欄高度剛好跟輸入法一樣。個人感覺這種使用者體驗特別的好,別看這個細節小,但程式碼實現處理起來還是有一定難度。今天就帶大家來實現這種效果,下面是效果圖:

Android監聽輸入法並獲取高度——輸入法與頁面佈局無縫切換

監聽輸入法的彈起和隱藏

首先,我們需要知道輸入法的高度,使表情欄的高度與之保持一致。但是Android是沒有提供現成的介面給開發者監聽輸入法的狀態,因此需要自定義的KeyboardLayout,監聽佈局的改變。檢視樹ViewTreeObserver發生變化時會回撥OnGlobalLayoutListener.onGlobalLayout()方法,通過變化前後佈局高度差計算出輸入法的高度。

public class KeyboardLayout extends FrameLayout {

    private KeyboardLayoutListener mListener;
    private boolean mIsKeyboardActive = false; // 輸入法是否啟用
    private int mKeyboardHeight = 0; // 輸入法高度

    public KeyboardLayout(Context context) {
        this(context, null, 0);
    }

    public KeyboardLayout(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public KeyboardLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        // 通過檢視樹監聽佈局變化
        getViewTreeObserver().addOnGlobalLayoutListener(new KeyboardOnGlobalChangeListener());
    }

    private class KeyboardOnGlobalChangeListener implements ViewTreeObserver.OnGlobalLayoutListener {

        int mScreenHeight = 0;
        Rect mRect = new Rect();

        private int getScreenHeight() {
            if (mScreenHeight > 0) {
                return mScreenHeight;
            }
            mScreenHeight = ((WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE))
                    .getDefaultDisplay().getHeight();
            return mScreenHeight;
        }

        @Override
        public void onGlobalLayout() {
//            // 獲取當前頁面視窗的顯示範圍
            getWindowVisibleDisplayFrame(mRect);

            int screenHeight = getScreenHeight(); //螢幕高度
            int keyboardHeight = screenHeight - mRect.bottom; // 輸入法的高度
            boolean isActive = false;
            if (Math.abs(keyboardHeight) > screenHeight / 5) {
                isActive = true; // 超過螢幕五分之一則表示彈出了輸入法
                mKeyboardHeight = keyboardHeight;
            }
            mIsKeyboardActive = isActive;
            if (mListener != null) {
                mListener.onKeyboardStateChanged(isActive, keyboardHeight);
            }
        }
    }

    public void setKeyboardListener(KeyboardLayoutListener listener) {
        mListener = listener;
    }

    public KeyboardLayoutListener getKeyboardListener() {
        return mListener;
    }

    public boolean isKeyboardActive() {
        return mIsKeyboardActive;
    }

    /**
     * 獲取輸入法高度
     *
     * @return
     */
    public int getKeyboardHeight() {
        return mKeyboardHeight;
    }

    public interface KeyboardLayoutListener {
        /**
         * @param isActive       輸入法是否啟用
         * @param keyboardHeight 輸入法皮膚高度
         */
        void onKeyboardStateChanged(boolean isActive, int keyboardHeight);
    }

}
複製程式碼

使用

KeyboardLayout加入佈局檔案中即可,無其他使用限制。從程式碼中可知,當佈局變化時並不需要知道KeyboardLayout的高度來計算輸入法高度,KeyboardLayout只是充當一個佈局監聽器的作用。

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
             android:layout_width="match_parent"
             android:layout_height="match_parent">
...
    <cn.forward.androids.views.KeyboardLayout
        android:id="@+id/keyboard_layout"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>
...
</FrameLayout>
複製程式碼
mKeyboardLayout = (KeyboardLayout) findViewById(R.id.keyboard_layout);
mKeyboardLayout.setKeyboardListener(new KeyboardLayout.KeyboardLayoutListener() {
            @Override
            public void onKeyboardStateChanged(boolean isActive, int keyboardHeight) {
                if (isActive) { // 輸入法開啟
                    //do something
                }else {

                }
            }
        });
複製程式碼

輸入法與頁面佈局無縫切換

輸入法和表情欄切換時,如果只是簡單的在切換到輸入法時隱藏表情欄,或者切換到表情欄時隱藏輸入法,這樣過度過程造成佈局閃爍一下,如下所示:

Android監聽輸入法並獲取高度——輸入法與頁面佈局無縫切換

這樣的效果簡直會逼死像我這樣有強迫症的人,因此我們需要解決它!造成這種問題的原因是,在顯示錶情欄時,輸入法還沒消失,因此表情欄會出現在輸入法上面,當輸入法消失時,表情欄的位置又被重新調整到底部,因此會造成佈局閃爍,同理可以解釋切換到輸入法時造成閃爍的原因。解決問題的關鍵主要靠如下兩句程式碼:

// 設定輸入法彈起時自動調整佈局,使之在輸入法之上
getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
// 設定輸入法彈起時不調整當前佈局
getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING);
複製程式碼

當從輸入法切換到表情欄時,設定佈局為不會重新調整SOFT_INPUT_ADJUST_NOTHING,同時隱藏輸入法,顯示錶情欄,這樣當再次切換輸入法時,剛好輸入法可以擋住表情欄,再把佈局設為可自動調整SOFT_INPUT_ADJUST_RESIZE,程式碼如下:

public class KeyboardLayoutDemo extends Activity {
    private KeyboardLayout mKeyboardLayout;
    private View mEmojiView;
    private Button mEmojiBtn;
    private EditText mInput;

    int mKeyboardHeight = 400; // 輸入法預設高度為400

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_keyboard_layout);

        // 起初的佈局可自動調整大小
        getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE | WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);

        mKeyboardLayout = (KeyboardLayout) findViewById(R.id.keyboard_layout);
        mEmojiView = findViewById(R.id.emoji);
        mEmojiBtn = (Button) findViewById(R.id.emoji_btn);

        mInput = (EditText) findViewById(R.id.input);

        // 點選輸入框
        mInput.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mKeyboardLayout.postDelayed(new Runnable() {
                    @Override
                    public void run() { // 輸入法彈出之後,重新調整
                        mEmojiBtn.setSelected(false);
                        mEmojiView.setVisibility(View.GONE);
                        getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
                    }
                }, 250); // 延遲一段時間,等待輸入法完全彈出
            }
        });

        mEmojiBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mEmojiBtn.setSelected(!mEmojiBtn.isSelected());

                if (mKeyboardLayout.isKeyboardActive()) { // 輸入法開啟狀態下
                    if (mEmojiBtn.isSelected()) { // 開啟表情
                        getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING); //  不改變佈局,隱藏鍵盤,emojiView彈出
                        InputMethodManager imm = (InputMethodManager) v.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
                        imm.hideSoftInputFromWindow(mInput.getApplicationWindowToken(), 0);
                        mEmojiView.setVisibility(View.VISIBLE);
                    } else {
                        mEmojiView.setVisibility(View.GONE);
                        InputMethodManager imm = (InputMethodManager) v.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
                        imm.hideSoftInputFromWindow(mInput.getApplicationWindowToken(), 0);
                        getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
                    }
                } else { //  輸入法關閉狀態下
                    if (mEmojiBtn.isSelected()) {
                        // 設定為不會調整大小,以便輸入彈起時佈局不會改變。若不設定此屬性,輸入法彈起時佈局會閃一下
                        getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING);
                        mEmojiView.setVisibility(View.VISIBLE);
                    } else {
                        getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
                        mEmojiView.setVisibility(View.GONE);
                    }
                }
            }
        });

        mKeyboardLayout.setKeyboardListener(new KeyboardLayout.KeyboardLayoutListener() {
            @Override
            public void onKeyboardStateChanged(boolean isActive, int keyboardHeight) {

                if (isActive) { // 輸入法開啟
                    if (mKeyboardHeight != keyboardHeight) { // 鍵盤發生改變時才設定emojiView的高度,因為會觸發onGlobalLayoutChanged,導致onKeyboardStateChanged再次被呼叫
                        mKeyboardHeight = keyboardHeight;
                        initEmojiView(); // 每次輸入法彈起時,設定emojiView的高度為鍵盤的高度,以便下次emojiView彈出時剛好等於鍵盤高度
                    }
                    if (mEmojiBtn.isSelected()) { // 表情開啟狀態下
                        mEmojiView.setVisibility(View.GONE);
                        mEmojiBtn.setSelected(false);
                    }
                }
            }
        });
    }

    // 設定表情欄的高度
    private void initEmojiView() {
        ViewGroup.LayoutParams layoutParams = mEmojiView.getLayoutParams();
        layoutParams.height = mKeyboardHeight;
        mEmojiView.setLayoutParams(layoutParams);
    }
}
複製程式碼

專案原始碼:Github

github.com/1993hzw/And…

(Androids是本人根據平時的專案實踐經驗,為了提高Android開發效率而寫的一個工具SDK;裡面提供了一些工具類以及自定義View,可在實際專案開發時直接使用。)

Android監聽輸入法並獲取高度——輸入法與頁面佈局無縫切換

相關文章