阅读 61

Android监听输入法并获取高度——输入法与页面布局无缝切换

在QQ或者微信的聊天页面,当输入法和表情栏互相切换时,过度非常自然,而且表情栏高度刚好跟输入法一样。个人感觉这种用户体验特别的好,别看这个细节小,但代码实现处理起来还是有一定难度。今天就带大家来实现这种效果,下面是效果图:

监听输入法的弹起和隐藏

首先,我们需要知道输入法的高度,使表情栏的高度与之保持一致。但是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 {

                }
            }
        });
复制代码

输入法与页面布局无缝切换

输入法和表情栏切换时,如果只是简单的在切换到输入法时隐藏表情栏,或者切换到表情栏时隐藏输入法,这样过度过程造成布局闪烁一下,如下所示:

这样的效果简直会逼死像我这样有强迫症的人,因此我们需要解决它!造成这种问题的原因是,在显示表情栏时,输入法还没消失,因此表情栏会出现在输入法上面,当输入法消失时,表情栏的位置又被重新调整到底部,因此会造成布局闪烁,同理可以解释切换到输入法时造成闪烁的原因。解决问题的关键主要靠如下两句代码:

// 设置输入法弹起时自动调整布局,使之在输入法之上
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,可在实际项目开发时直接使用。)

评论
说说你的看法