Android让我的迷惑的windowSoftInputMode(二)

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:windowSoftInputModeAndroidManifest.xml 中的 Activity 配置中并不会影响 Dialog 的行为,原因在于 Dialog 是一个独立的窗口 ,它与 Activity 有不同的窗口管理和布局行为。具体来说,ActivityDialog 是两个不同的窗口,它们的软键盘显示行为是由不同的机制控制的。

1. ActivitywindowSoftInputMode 控制范围

android:windowSoftInputMode 属性是用来控制 Activity 中软键盘的行为的。这个属性指定了当软键盘弹出时,Activity 的布局如何响应。常用的值包括:

  • adjustResize :当软键盘弹出时,Activity 会调整大小(通常是收缩),以便显示软键盘,而不被遮挡。
  • adjustPan :当软键盘弹出时,Activity 的内容会被平移(向上推),确保软键盘不遮挡焦点区域。

windowSoftInputModeActivity 的布局有效,但对 Dialog 无效。这是因为:

  • Dialog 本质上是一个 独立的窗口 ,它是由 WindowManager 创建的一个新的窗口,与 Activity 是两个不同的窗口层级。Dialog 视图的调整、软键盘行为和窗口管理与 Activity 的配置无关。
  • 即使 Activity 配置了 android:windowSoftInputMode="adjustResize",这只会影响 Activity 的主视图的行为,而 Dialog 会有自己的软键盘处理方式。

2. Dialog 的软键盘行为与 Activity 的设置无关

Dialog 中,软键盘的显示行为通常会由 Dialog 自身的 Window 设置来控制。即使您在 ActivityManifest 配置中设置了 windowSoftInputMode,这并不会传递到 Dialog 中的窗口。

对于 Dialog(包括 BottomSheetDialog 等),您需要手动设置窗口的软键盘行为。比如:

  • 使用 Window.setSoftInputMode() 来控制软键盘的行为。
  • 使用 OnApplyWindowInsetsListener 动态调整布局,避免软键盘遮挡。

3. WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZESOFT_INPUT_ADJUST_PAN

这些设置对 ActivityDialog 都是有效的,但需要 在 DialogWindow 上单独设置。例如:

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 无法继承 ActivitywindowSoftInputMode 配置?

Activity 是应用程序的核心组件之一,它的窗口和视图管理有一套独立的机制,并且在启动时,windowSoftInputMode 配置会被应用到整个 Activity,但是 Dialog 是作为 Activity 的一个子窗口存在,它有自己的窗口生命周期和 Window 配置。因此,Activity 中的配置并不会直接影响到 Dialog

原因很明显了:

1.android:windowSoftInputMode 仅对 Activity 有效,不会影响 Dialog,因为它们是不同的窗口,拥有不同的窗口管理和软键盘行为控制机制。

2.对于 Dialog,需要单独在 DialogWindow 上设置 setSoftInputMode() 或使用 setOnApplyWindowInsetsListener 监听软键盘行为。

3.DialogActivity 是不同的窗口层级,因此 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之后引入了WindowInsetsWindowInsets.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的灵活度高,自定义空间更大吧。

相关推荐
Yang-Never3 小时前
Shader -> SweepGradient扫描渐变着色器详解
android·java·kotlin·android studio·着色器
CYRUS_STUDIO4 小时前
Android Dex VMP 动态加载加密指令流
android·安全·逆向
tmacfrank6 小时前
Kotlin 协程基础十 —— 协作、互斥锁与共享变量
android·开发语言·kotlin
我命由我123457 小时前
Android Studio 警告信息:Use start instead of left to ensure...
android·java·开发语言·ide·java-ee·android studio·android-studio
沈剑心7 小时前
gson很好,但我劝你在Kotlin上使用kotlinx.serialization
android·java·kotlin
FatherOfCodingMan8 小时前
unity adb 连不上安卓手机?
android·adb·智能手机
daban20088 小时前
【技术支持】安卓无线adb调试连接方式
android·adb
SCBAiotAigc8 小时前
Android Studio历史版本包加载不出来,怎么办?
android·ide·android studio
十二测试录10 小时前
Android SDK下载安装(图文详解)
android·经验分享·python·程序人生·adb·自动化