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
来判断,理由如下:
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;
}
参考
不可靠
)仅做参考哈