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的灵活度高,自定义空间更大吧。

相关推荐
还鮟4 小时前
CTF Web的数组巧用
android
小蜜蜂嗡嗡5 小时前
Android Studio flutter项目运行、打包时间太长
android·flutter·android studio
aqi005 小时前
FFmpeg开发笔记(七十一)使用国产的QPlayer2实现双播放器观看视频
android·ffmpeg·音视频·流媒体
zhangphil7 小时前
Android理解onTrimMemory中ComponentCallbacks2的内存警戒水位线值
android
你过来啊你7 小时前
Android View的绘制原理详解
android
移动开发者1号10 小时前
使用 Android App Bundle 极致压缩应用体积
android·kotlin
移动开发者1号10 小时前
构建高可用线上性能监控体系:从原理到实战
android·kotlin
ii_best15 小时前
按键精灵支持安卓14、15系统,兼容64位环境开发辅助工具
android
美狐美颜sdk15 小时前
跨平台直播美颜SDK集成实录:Android/iOS如何适配贴纸功能
android·人工智能·ios·架构·音视频·美颜sdk·第三方美颜sdk
恋猫de小郭20 小时前
Meta 宣布加入 Kotlin 基金会,将为 Kotlin 和 Android 生态提供全新支持
android·开发语言·ios·kotlin