Android 软键盘显示隐藏判断

Android软键盘始终感觉是个BUG,难缠

用起来不顺手,每次应用版本涉及到相关问题,总是很尴尬

只能静下心好好梳理一下

1. 软键盘显示原理

软键盘的本质是什么?软键盘其实是一个Dialog

InputMethodService为我们的输入法创建了一个Dialog,并且将该Dialog的Window的某些参数(如Gravity)进行了设置,使之能够在底部或者全屏显示。当我们点击输入框时,系统对活动主窗口进行调整,从而为输入法腾出相应的空间,然后将该Dialog显示在底部,或者全屏显示

那么软键盘显示策略是怎样设计的了?

2. 软键盘显示策略

我们经常会在Activity中设置类似属性

// windowSoftInputMode 这个属性用于设置Activity主窗口与软键盘的交互模式
// 用于避免软键盘遮挡内容的问题
<activity
.....
 android:windowSoftInputMode="adjustResize" />

// 也有这种组合值的设置
// 该属性可选的值有两部分,一部分为软键盘的状态控制,控制软键盘是隐藏还是显示,
// 另一部分即交互模式:对Activity窗口的调整,以便腾出空间展示软键盘
<activity
 ......
 android:windowSoftInputMode=”stateUnchanged|adjustPan” />

android:windowSoftInputMode的属性设置必须是下面中的一个值,或一个state值加一个adjust值的组合,各个值之间用 | 分开

2.1 state值(控制显示或隐藏)

stateUnspecified (未指定状态)
当我们没有设置android:windowSoftInputMode属性的时候,软件默认采用的就是这种状态,系统会根据界面采取相应的软键盘的显示模式

stateUnchanged (不改变状态)
当前界面的软键盘状态,取决于上一个界面的软键盘状态,无论是隐藏还是显示

stateHidden (隐藏状态)
当设置该状态时,软键盘总是被隐藏,不管是否有输入的需求

stateAlwaysHidden (总是隐藏状态)
当设置该状态时,软键盘总是被隐藏,和stateHidden不同的是,当我们跳转到下个界面,如果下个页面的软键盘是显示的,而我们再次回来的时候,软键盘就会隐藏起来

stateVisible (可见状态)
当设置为这个状态时,软键盘总是可见的,即使在界面上没有输入框的情况下也可以强制弹出来出来

stateAlwaysVisible (总是显示状态)
当设置为这个状态时,软键盘总是可见的,和stateVisible不同的是,当我们跳转到下个界面,如果下个页面软键盘是隐藏的,而我们再次回来的时候,软键盘就会显示出来

2.2 adjust

adjustNoting
软键盘弹出时,主窗口不会做出任何反应

adjustPan (默认模式)
该模式下系统会通过布局的移动,来保证用户要进行输入的输入框肯定在用户的视野范围里面,从而让用户可以看到自己输入的内容

adjustUnspecified (未指定模式)
设置软键盘与软件内容之间的显示关系。默认的设置模式。在这种情况下,系统会根据界面选择不同的模式

adjustResize (调整模式)
该模式下窗口总是调整屏幕的大小用以保证软键盘的显示空间;这个选项不能和adjustPan同时使用,如果这两个属性都没有被设置,系统会根据窗口中的布局自动选择其中一个

以上组合参数设置,我们可以通过以下样例代码来一一测试验证一下

3. 上述点2提到相关参数的样例代码

无滑动布局:

<LinearLayout
        ......>

        <EditText
            android:id="@+id/et1"
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:text="文本输入框1" />
            // 此处很多布局
            ...... 
        
            // 1. 当Activity设置了adjustNoting,editTxt100获取焦点,弹出键盘是遮盖布局的
            // 2. 当Activity设置了adjustPan时,软键盘弹出主窗口布局会上移至直到显示editTxt100
            // 3. 当Activity设置了adjustUnspecified时,点击editTxt100,主窗口上移来保持
            // editTxt100在软键盘之上,这时adjustUnspecified的表现形式与adjustPan相同
            // 所以在无滑动的控件上,默认的指定形式为adjustPan
            // 4. 当Activity设置为adjustResize时,发现软键盘弹出的状态与adjustNoting表现一致。
            // 当设置adjustResize时,布局会为了软键盘弹出而重新绘制给软键盘留出空间,而由于控件
            // 无法滑动,所以表现的形式与adjustNoting一致
        <EditText
            android:id="@+id/editTxt100"
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:text="文本输入框12" />
</LinearLayout>

有滑动的布局

<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    ......>
    <LinearLayout
        ......>

        <EditText
            android:id="@+id/et1"
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:text="文本输入框1" />
            // 此处很多布局
            ...... 
        
            // 1. 当Activity设置了adjustNoting,editTxt100获取焦点,弹出键盘是遮盖布局的
            // 2. 当Activity设置了adjustPan时,软键盘弹出主窗口布局会上移至直到
            // 显示editTxt100(注意此时是主布局上移为输入框腾出布局空间)
            // 3. 当Activity设置了adjustUnspecified时,点击editTxt100,主窗口上移来保持
            // editTxt100在软键盘之上(注意上移的是ScrollView,如果ScrollView不是根布局,
            // 则主窗口是不会上移的,只会移动ScrollView)
            // 4. 当Activity设置了adjustResize时,会重绘ScrollView,腾出输入框布局空间
        <EditText
            android:id="@+id/editTxt100"
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:text="文本输入框12" />
    </LinearLayout>
</ScrollView>

在使用键盘过程中,我们还有些额外需求,例如:判断键盘此时是否可见

4. 如何判断软键盘是否在显示?

4.1 第一种:getWindow().getAttributes() (无效)

// 这个逻辑不正确(无效)
if(getWindow().getAttributes().softInputMode == 
	WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE){
    Toast.makeText(Main3Activity.this, "显示", Toast.LENGTH_SHORT).show();
} else {
    Toast.makeText(Main3Activity.this, "没显示", Toast.LENGTH_SHORT).show();
}

4.2 第二种:inputMethodManager.isActive (无效)

// 这个isActive方法,只要editText有焦点,它就返回true(无效)
 if(inputMethodManager.isActive(editText)){
    Toast.makeText(Main3Activity.this, "显示", Toast.LENGTH_SHORT).show();
} else {
    Toast.makeText(Main3Activity.this, "没显示", Toast.LENGTH_SHORT).show();
}

4.3 第三种:屏幕实际高度 - 可视化区域高度 (有效)

上述2种方法判断都不可靠,目前我觉得可靠的一种方案如下:

在Android我们是可以拿到屏幕中可视区域高度的,如果我们拿到屏幕高度(包括状态栏和虚拟导航栏),然后减去可视区域的高度(一般减少的高度是被键盘,虚拟键盘占据了的),通过这个值来判断键盘是否展示,实现步骤如下:

Step 1:先拿到屏幕高度(包括顶部状态栏,底部虚拟导航栏(如果有的话))

		final View decorView = mActivity.getWindow().getDecorView();
        Rect rect = new Rect();
        decorView.getWindowVisibleDisplayFrame(rect);
        // 获取屏幕的高度(包括状态栏,导航栏)
        int screenHeight = decorView.getRootView().getHeight();

或者

除了上面所说的通过getDecorView获取外,还可以通过如下方法:

	/**
     * @param context
     * @return 获取屏幕实际高度(包括导航栏 , 状态栏)
     * 

* 1. HUAWEI_EVA-AL00 * 1.1 无论底部虚拟导航栏展示,隐藏,均返回最大实际高度 * 2. MI 8 SE | MI MAX 3 * 2.1 无论底部虚拟导航栏展示,隐藏,均返回最大实际高度 * 3. Cool pad Y803 * 3.1 无论底部虚拟导航栏展示,隐藏,均返回最大实际高度 */ public static int getScreenRealH(Context context) { DisplayMetrics dm = new DisplayMetrics(); ((Activity) context).getWindowManager().getDefaultDisplay().getRealMetrics(dm); return dm.heightPixels; }

或者

	// 或者如下反射方法
    @Deprecated
    public static int getRealScreenHeight(Context context) {
        int dpi = 0;
        Display display = ((Activity)((Activity)context)).getWindowManager().getDefaultDisplay();
        DisplayMetrics dm = new DisplayMetrics();

        try {
            Class c = Class.forName("android.view.Display");
            Method method = c.getMethod("getRealMetrics", DisplayMetrics.class);
            method.invoke(display, dm);
            dpi = dm.heightPixels;
        } catch (Exception var6) {
            var6.printStackTrace();
        }

        return dpi;
    }

Step 2:屏幕高度减去可视区域高度和虚拟导航栏高度之和

		// 底部导航栏高度
        int navigatorHeight = getNavigatorHeight(mActivity);

        int keySoftHeight;
        if (isNavigationBarShowing(mActivity)) {
            // 导航栏存在
            keySoftHeight = screenHeight - rect.bottom - navigatorHeight;
        } else {
            keySoftHeight = screenHeight - rect.bottom;
        }

当然,如果上述方法中,isNavigationBarShowing()方法能准确返回导航栏是否展示的状态时,获取到的keySoftHeight就是准确的,如果大于0基本上可以判断:软键盘显示了。

很遗憾,这个方法不可信

所以可视化区域高度我们只能获取一个近似数值

// 不用判断导航栏是否展示,直接屏幕实际高度减去可视化区域高度
keySoftHeight = screenHeight - rect.bottom;

// 下面是我测试手头几款手机得出的可视化区域高度
	 * 1. MI 8 SE
     * <p>
     * 1.1 导航栏高度:130
     * 1.2 屏幕实际高度:2244
     * 1.3 导航栏正常展示时:键盘隐藏时:rect.bottom: 2114
     * 1.4 导航栏正常展示时:键盘展示时:rect.bottom: 1276
     * 1.5 导航栏正常隐藏时:键盘隐藏时:rect.bottom: 2244
     * 1.6 导航栏正常隐藏时,键盘展示时:rect.bottom: 1346
     * 1.7 得出键盘高度(得出键盘高度不一样,是该款小米手机在有无导航栏场景下,确实键盘高度底部有空白高度间隔(60),确实键盘高度不一样)1.4 情况下:838(2244-1276-130) 
     	   1.6 情况下:898(2244-1346) 

	 * 2. HuaWei EVA AL00
     * <p>
     * 1.1 导航栏高度:128
     * 1.2 屏幕实际高度:1920
     * 1.3 导航栏正常展示时:键盘隐藏时:rect.bottom: 1792
     * 1.4 导航栏正常展示时:键盘展示时:rect.bottom: 961
     * 1.5 导航栏正常隐藏时:键盘隐藏时:rect.bottom: 1920
     * 1.6 导航栏正常隐藏时,键盘展示时:rect.bottom: 1089
     * 1.7 得出键盘高度(这款手机得出键盘高度是一致的)1.4 情况下:831(1920-961-128) 
     	   1.6 情况下:831(1920-1089) 

	 * 3. MeiZu 7 Plus
     * <p>
     * 1.1 导航栏高度:168
     * 1.2 屏幕实际高度:2560
     * 1.3 导航栏正常展示时:键盘隐藏时:rect.bottom: 2392
     * 1.4 导航栏正常展示时:键盘展示时:rect.bottom: 1380
     * 1.5 导航栏正常隐藏时:键盘隐藏时:rect.bottom: 2560
     * 1.6 导航栏正常隐藏时,键盘展示时:rect.bottom: 1548
     * 1.7 得出键盘高度(魅族手机得出键盘高度也是一致的)1.4 情况下:1012(2560-1380-168) 
     	   1.6 情况下:1012(2560-1548) 

	 * 4. Oppo R9s
     * <p>
     * 1.1 导航栏高度:120
     * 1.2 屏幕实际高度:1920
     * 1.3 导航栏正常展示时:键盘隐藏时:rect.bottom: 1800
     * 1.4 导航栏正常展示时:键盘展示时:rect.bottom: 1098
     * 1.5 导航栏正常隐藏时:键盘隐藏时:rect.bottom: 1920
     * 1.6 导航栏正常隐藏时,键盘展示时:rect.bottom: 1218
     * 1.7 得出键盘高度(Oppo手机得出键盘高度也是一致的)1.4 情况下:702(1920-1098-120) 
     	   1.6 情况下:702(1920-1218) 

	 * 5. MEITU MP 1602
     * <p>
     * 1.1 导航栏高度:144
     * 1.2 屏幕实际高度:1920
     * 1.3 导航栏正常展示时:键盘隐藏时:rect.bottom: 1776
     * 1.4 导航栏正常展示时:键盘展示时:rect.bottom: 937
     * 1.5 导航栏正常隐藏时:键盘隐藏时:rect.bottom: 1920
     * 1.6 导航栏正常隐藏时,键盘展示时:rect.bottom: 1081
     * 1.7 得出键盘高度(MEITU手机得出键盘高度是一致的)1.4 情况下:839(1920-937-144) 
     	   1.6 情况下:839(1920-1081) 

Step 3:然后完整方法如下:

	/**
     * 得到当前软键盘的高度
     *
     * @return 软键盘的高度
     */
    public int getCurrentSoftInputHeight() {
        final View decorView = mActivity.getWindow().getDecorView();
        Rect rect = new Rect();
        decorView.getWindowVisibleDisplayFrame(rect);
        // 获取屏幕的高度(包括状态栏,导航栏)
        int screenHeight = decorView.getRootView().getHeight();
        int keySoftHeight = screenHeight - rect.bottom;
        return keySoftHeight;
    }

实际项目中,我采用的是getCurrentSoftInputHeight()方法返回值大于 200px来判断,理由如下:

  1. 一般虚拟导航栏之类的高度不会大于200px(如:导航栏展示时,keySoftHeight值至多也等于导航栏高度)
  2. 若大于200px,基本上可以判断键盘是展示出来了,上面测试了5款手机,导航栏高度均小于200px,后续或持续验证多款手机,来设置一个合理边界值进行判断

Step 4:补充一下导航栏相关的2个方法

	/**
     * 获取导航栏高度即虚拟按键的高度
     *
     * @param context
     * @return
     */
    public static int getNavigatorHeight(Context context) {
        int rid = context.getResources().getIdentifier(
                "config_showNavigationBar", "bool", "android");
        if (rid != 0) {
            int resourceId = context.getResources().getIdentifier(
                    "navigation_bar_height", "dimen", "android");
            return context.getResources().getDimensionPixelSize(resourceId);
        }
        return 0;
    }

参考

  1. Android软键盘(五)如何判断软键盘是否显示
  2. 安卓开发——如何判断软键盘是否弹出(显示)
  3. Android判断软键盘弹出并隐藏的简单完美解决方案 (不可靠)
  4. Android 软键盘的显示和隐藏,这样操作就对了
  5. 获取android软键盘高度 仅做参考哈
  6. 彻底搞定Android开发中软键盘的常见问题
  7. Android手动显示和隐藏软键盘方法总结
  8. Android软键盘的全面解析,让你不再怕控件被遮盖

你可能感兴趣的:(Android,基础,Android,SoftHeight,软键盘展示隐藏)