Android让我的迷惑的windowSoftInputMode(二)
很好,又是windowSoftInputMode
,我也不知道到底要被这个玩意折磨多少遍,接着上篇Android让我的迷惑的windowSoftInput 这玩意又又又出问题了,不过这次主要的锅是我自己对Android的一些知识了解的不够全面(简直是可恶至极)
场景还原:
还是那个情况,屏幕底部的输入框在获取焦点后,软键盘会弹出并覆盖一部分区域,如下图
其实根据Android让我的迷惑的windowSoftInput这个里面的规则,我设置android:windowSoftInputMode="adjustResize"
就好了,于是我在AndroidManifest.xml
中对应的Activity中加入了android:windowSoftInputMode="adjustResize"
,但是怎么设置都没有效果,和原来成功的案例仔细一对比,我感觉锅应该是在底部这个View
的显示方式上
kotlin
R.id.moment_action_comment -> {
// 显示底部评论对话框
val bottomSheetDialog = BottomSheetDialog(v.context)
val contentView =
LayoutInflater.from(v.context).inflate(R.layout.comment_bottom_view, null)
bottomSheetDialog.setContentView(contentView)
contentView.findViewById<TextView>(R.id.moment_list_comment_msg_send_bt)
.setOnClickListener {
val commentEdit =
contentView.findViewById<EditText>(R.id.moment_list_comment_msg_edit)
if (commentEdit.useless("不能评论空内容")) return@setOnClickListener
// 添加评论
NetApi.addMomentComment(App.account,
holder.vm.moment.id,
commentEdit.take(), // 获取输入的评论内容并清除评论
success = {
// 获取好友动态列表并更新数据
holder.vm.changeComments(it)
AppExecutor.mainHandler().post {
//隐藏键盘
hideKeyboard(contentView)
bottomSheetDialog.dismiss()
}
},
fail = { msg ->
ToastUtil.showToast(v.context, msg)
AppExecutor.mainHandler().post {
bottomSheetDialog.dismiss()
}
})
}
bottomSheetDialog.show()
}
如上代码,这个底部输入框是以BottomSheetDialog的方式显示的,我总感觉是这个问题,于是去问了问GPT,果不其然和我预料的一样
GPT的回答:(关键原因我已经标红了)
android:windowSoftInputMode
在 AndroidManifest.xml
中的 Activity
配置中并不会影响 Dialog
的行为,原因在于 Dialog
是一个独立的窗口 ,它与 Activity
有不同的窗口管理和布局行为。具体来说,Activity
和 Dialog
是两个不同的窗口,它们的软键盘显示行为是由不同的机制控制的。
1. Activity
的 windowSoftInputMode
控制范围
android:windowSoftInputMode
属性是用来控制 Activity
中软键盘的行为的。这个属性指定了当软键盘弹出时,Activity
的布局如何响应。常用的值包括:
adjustResize
:当软键盘弹出时,Activity
会调整大小(通常是收缩),以便显示软键盘,而不被遮挡。adjustPan
:当软键盘弹出时,Activity
的内容会被平移(向上推),确保软键盘不遮挡焦点区域。
windowSoftInputMode
对 Activity
的布局有效,但对 Dialog
无效。这是因为:
Dialog
本质上是一个 独立的窗口 ,它是由WindowManager
创建的一个新的窗口,与Activity
是两个不同的窗口层级。Dialog
视图的调整、软键盘行为和窗口管理与Activity
的配置无关。- 即使
Activity
配置了android:windowSoftInputMode="adjustResize"
,这只会影响Activity
的主视图的行为,而Dialog
会有自己的软键盘处理方式。
2. Dialog
的软键盘行为与 Activity
的设置无关
在 Dialog
中,软键盘的显示行为通常会由 Dialog
自身的 Window
设置来控制。即使您在 Activity
的 Manifest
配置中设置了 windowSoftInputMode
,这并不会传递到 Dialog
中的窗口。
对于 Dialog
(包括 BottomSheetDialog
等),您需要手动设置窗口的软键盘行为。比如:
- 使用
Window.setSoftInputMode()
来控制软键盘的行为。 - 使用
OnApplyWindowInsetsListener
动态调整布局,避免软键盘遮挡。
3. WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE
和 SOFT_INPUT_ADJUST_PAN
这些设置对 Activity
和 Dialog
都是有效的,但需要 在 Dialog
的 Window
上单独设置。例如:
scss
java复制代码BottomSheetDialog dialog = new BottomSheetDialog(context);
// 设置软键盘行为
dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
// 设置内容视图
dialog.setContentView(R.layout.dialog_bottom_sheet);
// 显示 Dialog
dialog.show();
4. 为何 Dialog
无法继承 Activity
的 windowSoftInputMode
配置?
Activity
是应用程序的核心组件之一,它的窗口和视图管理有一套独立的机制,并且在启动时,windowSoftInputMode
配置会被应用到整个 Activity
,但是 Dialog
是作为 Activity
的一个子窗口存在,它有自己的窗口生命周期和 Window
配置。因此,Activity
中的配置并不会直接影响到 Dialog
。
原因很明显了:
1.android:windowSoftInputMode
仅对 Activity
有效,不会影响 Dialog
,因为它们是不同的窗口,拥有不同的窗口管理和软键盘行为控制机制。
2.对于 Dialog
,需要单独在 Dialog
的 Window
上设置 setSoftInputMode()
或使用 setOnApplyWindowInsetsListener
监听软键盘行为。
3.Dialog
和 Activity
是不同的窗口层级,因此 Activity
的配置不会影响到 Dialog
。
所以,我们可以单独给BottomSheetDialog
单独设置windowSoftInputMode
kotlin
// 显示底部评论对话框
val bottomSheetDialog = BottomSheetDialog(v.context)
val contentView =
LayoutInflater.from(v.context).inflate(R.layout.comment_bottom_view, null)
bottomSheetDialog.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE)
bottomSheetDialog.setContentView(contentView)
主要是加了bottomSheetDialog.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE)
这句,然后就正常了
但是,Android Studio
提示我们这个字段已经被废弃了???
点进去看一看原因
按照官方的提示,我们应该使用View.setOnApplyWindowInsetsListener()
方法,去网络上查看了一下使用方法,基本都是下面这种用法
java
// 监听软键盘插入区域变化
bottomSheet.setOnApplyWindowInsetsListener((v, insets) -> {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
// 获取软键盘的插入区域
Insets imeInsets = insets.getInsets(WindowInsets.Type.ime());
// 设置底部填充来避免软键盘遮挡
v.setPadding(0, 0, 0, imeInsets.bottom);
}
return insets;
});
原理大致如此:基于 WindowInsets
来获取软键盘的插入区域,并通过设置布局的 padding
来确保软键盘显示时,视图的底部不会被遮挡。
ini
Insets imeInsets = insets.getInsets(WindowInsets.Type.ime());
这句话的作用是获取与软键盘(IME,即输入法)相关的插入区域。具体来说,WindowInsets
类中的 Type.ime()
用来指示与输入法(软键盘)相关的插入区域。WindowInsets.Type.ime()
:代表软键盘区域。通过该方法,可以获得软键盘显示时所占用的屏幕区域,通常这个区域是底部的一部分,表示软键盘的高度。imeInsets
是一个 Insets
对象,它包含了软键盘占据的区域。对于软键盘,我们最关心的是底部插入(imeInsets.bottom
),即软键盘高度。这个值会告诉你软键盘的高度,单位通常是像素。
java
v.setPadding(0, 0, 0, imeInsets.bottom);
这行代码通过设置视图 v
的底部填充(paddingBottom
)来避免软键盘遮挡视图的内容。setPadding(left, top, right, bottom)
用于设置视图的四个方向的填充。这里的 imeInsets.bottom
是软键盘占用的高度,它用于设置视图的底部填充。设置了底部填充后,视图的内容会向上推移,与软键盘的高度匹配,确保视图的底部部分不会被软键盘遮挡。也就是说,在键盘弹出之后,设置View的bottom_padding为键盘的高度,这样View就不会被键盘遮挡了。
于是我将我的代码做了如下修改:
kotlin
val bottomSheetDialog = BottomSheetDialog(v.context)
val contentView =
LayoutInflater.from(v.context).inflate(R.layout.comment_bottom_view, null)
contentView.fitsSystemWindows = false
contentView.setOnApplyWindowInsetsListener { v, insets ->
// 获取软键盘插入区域
val imeInsets: Insets = insets.getInsets(WindowInsets.Type.ime())
v.setPadding(0,0,0,imeInsets.bottom)
insets
}
bottomSheetDialog.setContentView(contentView)
重新运行了一下代码,确实没有遮挡了,但是有了个新的bug
仔细看上面的图片,键盘弹出后,BottomSheetDialog
和键盘之间多了一段空白???,看高度和键盘未弹出时底部的NavigtionBar
的的高度差不多。
Android 11之后引入了WindowInsets
,WindowInsets.Type
是 Android 11(API 级别 30)引入的一个枚举类,用于指定不同类型的窗口插入(Insets)区域。WindowInsets
类用于处理与窗口相关的插入区域,如状态栏、导航栏、软键盘等
类型 | 描述 | 用途 |
---|---|---|
WindowInsets.Type.statusBars() | 状态栏的插入区域。状态栏是设备屏幕顶部的区域,通常显示系统信息、通知等 | 用于确定状态栏占用的空间并相应调整布局 |
WindowInsets.Type.navigationBars() | 底部导航栏的插入区域。导航栏是设备屏幕底部的区域,通常包含返回、主页和多任务按钮(对于传统的虚拟导航栏)或手势导航区域(对于手势控制的设备) | 用于获取底部导航栏的高度或避免其覆盖布局元素 |
WindowInsets.Type.ime() | 软键盘(IME,输入法编辑器)占用的插入区域。当软键盘弹出时,会占用一定的屏幕区域 | 用于动态调整布局,使其不被软键盘覆盖,通常用于处理弹出软键盘时的布局调整 |
WindowInsets.Type.magnification() | 放大镜功能区域的插入空间。当启用放大镜功能时,屏幕的某些区域可能会被放大 | 用于处理放大镜视图占用的区域 |
WindowInsets.Type.captionBar() | 标题栏(或字幕栏)的插入区域,通常用于显示辅助功能或其他界面元素 | 用于处理字幕栏等 UI 元素的空间,避免它们与布局元素重叠 |
WindowInsets.Type.systemGestures() | 系统手势区域的插入空间。这通常是设备屏幕底部的手势控制区域,主要用于支持手势导航的设备 | 用于处理系统手势区域的空间,避免手势区域和其他视图重叠 |
WindowInsets.Type.all() | 所有类型的插入区域,包括状态栏、导航栏、软键盘等 | 用于一次性获取所有插入区域,适用于需要处理所有区域的场景 |
WindowInsets.Type.displayCutout() | 显示缺口区域(如刘海、打孔屏等)的插入区域 | 用于避免显示缺口覆盖关键内容或视图元素 |
按照上面gif图片中的情况,应该是将navigationBars
的高度也算上了,很疑惑的是,我只在代码中给View添加了底部的bottom_padding
,且这个值是imeInsets.bottom
,为啥会把navigationBars
也算上呢?希望有大佬告知一下
将代码作如下修改:
kotlin
contentView.setOnApplyWindowInsetsListener { v, insets ->
// 获取软键盘插入区域
val imeInsets: Insets = insets.getInsets(WindowInsets.Type.ime())
val navigationBarInsets = insets.getInsets(WindowInsets.Type.navigationBars())
v.setPadding(0,0,0,imeInsets.bottom - navigationBarInsets.bottom)
insets
}
效果如下,软件盘弹出后中间的那块空白没了,但是BottomSheetDialog
一出现时就被减去了navigationBarInsets.bottom
所以,正确的模板其实应该是这样的:
kotlin
contentView.setOnApplyWindowInsetsListener { v, insets ->
// 获取软键盘插入区域
val imeInsets: Insets = insets.getInsets(WindowInsets.Type.ime())
val navigationBarInsets =
insets.getInsets(WindowInsets.Type.navigationBars())
// 设置底部填充,防止键盘遮挡
if (imeInsets.bottom == 0) { //软键盘没有出来就正常显示
v.setPadding(0, 0, 0, 0)
} else {
v.setPadding(0, 0, 0, imeInsets.bottom - navigationBarInsets.bottom)
}
insets
}
这才对嘛,不过这样子感觉好复杂,有没有代码少效果又好的方法?有的,兄弟,有的,像这样的方法还有************
其实最简单的方法就是bottomSheetDialog.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE)
效果也一样,不过官方将这个字段弃用了,应该是setOnApplyWindowInsetsListener
的灵活度高,自定义空间更大吧。