Android:表情输入键盘实现探索

阅读时间 约 2 分钟

本篇文章思路最终的实现效果还不是很完美,更好的实现方法见Android: 类似QQ、微信的表情输入键盘实现思路一文。

需求

最近在写北邮人论坛客户端时,有一个需求是实现像手机QQ、微信那样的表情输入键盘,效果图:

demo

表情键盘本身并不难做,无非就是一个带SlidingTab的ViewPager,困扰我的地方在于,如何正确处理系统软键盘与表情键盘之间的显隐关系。

Google了一下,大概有这么几种思路:

第一种:动态改变SoftInputMode

这篇博文是国内网上转载比较多的方法,软键盘显示时将SoftInputMode设置为「stateVisible|adjustResize」,表情键盘显示时调整为「adjustPan」。

但在我实际使用过程中效果并不理想,一是我需要在一个ListView的底部实现表情键盘,这样动态更改SoftInputMode会导致ListView上下跳动;二是切换到别的界面再切换回来时软键盘的显隐状态偶尔会有冲突,最终我放弃了这种方法。

第二种:Dialog

Emoticons-Keyboard这个项目的实现方法是直接在软键盘上覆盖显示一个Dialog,避开了大部分的显示逻辑操作,思路非常独特,可惜我编译运行后发现显示效果并不好,除了动画效果,最大的问题仍然是是从别的界面切换过来时,与软键盘的显示有冲突

基本思路

上面提到的两个项目给了我很大的启发,我反复尝试了微信、微博、手机QQ等应用的表情键盘逻辑,发现它们切换键盘并不会导致ListView跳动,如果没有别的什么黑科技的话,基本可以断定使用的SoftInputMode就是adjustPan。(SoftInputMode各个属性值的意义

既然是adjustPan就好说了,软键盘显示的时候不会导致ListView跳动,那么Activity的底部必然有一个跟软键盘相同高度的View被软键盘覆盖了,这个View其实就是表情输入键盘,这样点击表情按钮的时候只需要显示隐藏软键盘,背后的表情框就显示出来了。

思路有了,接下来就是梳理一下所需要的技术点:

  • 如何检测软键盘高度(用于动态设置表情键盘的高度)?
  • 在代码中如何手动显示/隐藏软键盘?
  • 如何防止从别的界面切换过来时,软键盘状态改变了有可能导致的显示冲突?

如果这三个问题解决了,需求就基本实现了。

检测软键盘的高度

直接上代码:

private int getSupportSoftInputHeight() {
    Rect r = new Rect();
    mActivity.getWindow().getDecorView().getWindowVisibleDisplayFrame(r);
    int screenHeight = mContext.getWindow().getDecorView().getRootView().getHeight();
    int softInputHeight = screenHeight - r.bottom;
    if (Build.VERSION.SDK_INT >= 20) {
        // When SDK Level >= 20 (Android L), the softInputHeight will contain the height of softButtonsBar (if has)
        softInputHeight = softInputHeight - getSoftButtonsBarHeight();
    }
    return softInputHeight;
}

这里的原理是通过当前的Activity拿到其RootView的高度,减去Activity本身实际的高度,就等于软键盘的高度了。但在实际应用过程中发现,某些Android版本下,没有显示软键盘时减出来的高度总是144,而不是零,经过反复研究,最后发现这个高度是包括了虚拟按键栏的,所以在API Level高于18时,我们需要减去底部虚拟按键栏的高度(如果有的话)。

@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
private int getSoftButtonsBarHeight() {
    DisplayMetrics metrics = new DisplayMetrics();
    mContext.getWindowManager().getDefaultDisplay().getMetrics(metrics);
    int usableHeight = metrics.heightPixels;
    mContext.getWindowManager().getDefaultDisplay().getRealMetrics(metrics);
    int realHeight = metrics.heightPixels;
    if (realHeight > usableHeight) {
        return realHeight - usableHeight;
    } else {
        return 0;
    }
}

将高度设置给表情键盘就比较简单了:

LinearLayout.LayoutParams linearParams = (LinearLayout.LayoutParams) mEmotionLayout.getLayoutParams();
linearParams.height = getSupportSoftInputHeight();

在代码中手动显示、隐藏软键盘

也是直接上代码了,这两个方法也比较容易查到:

private void showSoftInput() {
    mInputManager.showSoftInput(mEditText, 0);
}

private void hideSoftInput() {
    mInputManager.hideSoftInputFromWindow(mEditText.getWindowToken(), 0);
}

解决切换程序时的显示冲突

在默认状态(StateUnspecified)下,在程序内打开软键盘然后点击Home键或多任务键切换出去时,软键盘会收起。再次进入程序界面也不会打开,前文提到的两个项目就是在这种情况下会出现问题。如何保证软键盘和表情键盘的同步,直观反应就是监听软键盘的高度变化,查了一下,果然可以监听:

mEditText.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
    @Override
    public void onGlobalLayout() {
        int softInputHeight = getSupportSoftInputHeight();
        if (softInputHeight != lastSoftInputHeight) {
            // do Something
        }
    }
});

实际测试中,这个函数在运行时会调用很多次,我们只需要在高度变化时做处理即可。

states

如上图,一共有三种状态,表情键盘的状态分别为:gone、invisible和visible。分别判断这三个状态之间的转化关系,然后动态的设置Visiblity即可:

public void onGlobalLayout() {
    int softInputHeight = getSupportSoftInputHeight();
    if (softInputHeight != lastSoftInputHeight) {
        if (softInputHeight <= 0) {
            lastSoftInputHeight = softInputHeight;
            if (!notHideEmojiLayout) {
                mEmotionLayout.setVisibility(View.GONE);
            } else {
                notHideEmojiLayout = false;
            }
        } else {
            lastSoftInputHeight = softInputHeight;
            LinearLayout.LayoutParams linearParams = (LinearLayout.LayoutParams) mEmotionLayout.getLayoutParams();
            linearParams.height = softInputHeight;
            mEmotionLayout.setVisibility(View.INVISIBLE);
            if (linearParams.height == softInputHeight) {
                mEmotionLayout.setVisibility(View.INVISIBLE);
            } else {
                linearParams.height = softInputHeight;
            }

            sp.edit().putInt(SHARE_PREFERENCE_TAG, softInputHeight).apply();
        }
    }
}

一点小Bug

由于Android设备的多样性,软键盘高度不一致,所以需要动态的设置表情键盘的高度,然而程序在第一次软键盘弹出后才能检测到软键盘高度,但这时由于表情键盘高度与软键盘不一致,会导致显示有点异常。所以程序会将检测到的高度保存到SharedPreference中,在Activity加载时读出高度即可。

不过即使是这样,在整个程序第一次进入这个界面时还是会显示异常,暂时的解决办法是在其他软键盘弹出的页面检测一次软键盘高度

如果你有更好的办法,请留言交流~