当前位置: 首页 > news >正文

android 如何判定底部导航栏显示时 不是键盘显示

在 Android 中判定底部导航栏是否显示时,核心痛点是 区分 “导航栏的底部 Insets” 和 “软键盘弹出的底部 Insets”—— 两者都会导致 getSystemWindowInsetBottom() 返回非零值,直接判断会误将键盘弹出当成导航栏显示。以下是基于 WindowInsets 类型区分 的精准解决方案,兼容不同 Android 版本和场景。

核心原理:通过 Insets 类型过滤键盘

Android 的 WindowInsets 会标记不同来源的 “插入区域”(如导航栏、状态栏、软键盘),通过 WindowInsetsCompat.Type 可精准过滤出 仅由导航栏贡献的底部 Insets,从而排除键盘干扰。

关键类型说明:

Insets 类型含义需排除 / 保留
Type.NAVIGATION_BARS系统导航栏(底部 / 侧边)保留(目标判断对象)
Type.IME软键盘(Input Method Editor)排除(干扰项)
Type.STATUS_BARS状态栏(顶部)排除(与底部无关)

方案实现:兼容高低版本的工具类

以下工具类支持 Android 14(API 34)及以下版本,通过 WindowInsetsCompat 统一处理 Insets 类型,精准判断导航栏可见性并获取高度。

import android.view.View;
import androidx.core.view.WindowInsetsCompat;/*** 精准判断底部导航栏是否显示(排除软键盘干扰)*/
public class NavigationBarChecker {/*** 判定底部导航栏当前是否可见(排除键盘)* @param rootView 页面根布局(如 Activity 的 contentView、Fragment 的根View)* @return true:导航栏显示;false:导航栏隐藏或当前是键盘弹出*/public static boolean isNavigationBarVisible(View rootView) {if (rootView == null) {return false;}// 1. 获取根View的WindowInsets(包含所有插入区域信息)WindowInsetsCompat insetsCompat = ViewCompat.getRootWindowInsets(rootView);if (insetsCompat == null) {return false; // 极端情况(如View未附着到窗口),返回隐藏}// 2. 关键:仅获取“导航栏”贡献的底部Insets(排除键盘、状态栏等)// Type.NAVIGATION_BARS:指定只计算导航栏的Insetsint navBarBottomInset = insetsCompat.getInsets(WindowInsetsCompat.Type.navigationBars()).bottom;// 3. 底部Insets > 0 说明导航栏在底部显示(若为侧边导航栏,bottom会是0,需额外判断left/right)return navBarBottomInset > 0;}/*** 获取底部导航栏的真实高度(排除键盘干扰)* @param rootView 页面根布局* @return 导航栏高度(px);0:导航栏隐藏*/public static int getNavigationBarHeight(View rootView) {if (rootView == null) {return 0;}WindowInsetsCompat insetsCompat = ViewCompat.getRootWindowInsets(rootView);if (insetsCompat == null) {return 0;}// 同样只取导航栏的底部Insets,即为导航栏高度return insetsCompat.getInsets(WindowInsetsCompat.Type.navigationBars()).bottom;}/*** (扩展)判断是否为软键盘弹出状态(辅助验证)* @param rootView 页面根布局* @return true:键盘显示;false:键盘隐藏*/public static boolean isKeyboardVisible(View rootView) {if (rootView == null) {return false;}WindowInsetsCompat insetsCompat = ViewCompat.getRootWindowInsets(rootView);if (insetsCompat == null) {return false;}// 仅判断“键盘”贡献的底部Insets:>0 说明键盘弹出//对core 版本有要求,太低找不到//dependencies {//implementation 'androidx.core:core:1.5.0'//}int keyboardBottomInset = insetsCompat.getInsets(WindowInsetsCompat.Type.ime()).bottom;return keyboardBottomInset > 0;}
}

关键细节说明

1. 为什么必须用 ViewCompat.getRootWindowInsets()
  • 避免直接调用 rootView.getRootWindowInsets():该方法在 API 23(Android 6.0)以上才可用,ViewCompat 会自动兼容低版本(API 14+),无需额外版本判断。
  • 确保获取的是 “根 View 的 Insets”:只有根布局(如 setContentView 传入的 View)能拿到完整的系统 Insets,子 View 可能因布局嵌套导致 Insets 被截断。
2. 如何处理 “侧边导航栏”(如平板横屏)?

部分设备(平板、折叠屏)在横屏时会将导航栏放在左侧 / 右侧,此时 bottom Insets 为 0,需额外判断 left 或 right

// 扩展:判断任意位置的导航栏是否可见(含侧边)
public static boolean isAnyNavigationBarVisible(View rootView) {if (rootView == null) return false;WindowInsetsCompat insetsCompat = ViewCompat.getRootWindowInsets(rootView);if (insetsCompat == null) return false;WindowInsetsCompat.Insets navInsets = insetsCompat.getInsets(WindowInsetsCompat.Type.navigationBars());// 左/右/下 任意一个方向有Insets,说明导航栏可见return navInsets.left > 0 || navInsets.right > 0 || navInsets.bottom > 0;
}
3. 兼容 Android 14(API 34)的新变化

Android 14 新增了 WindowInsets.Type.systemBars()(包含状态栏 + 导航栏),但 Type.navigationBars() 仍完全兼容,无需修改代码 ——WindowInsetsCompat 已内部适配新 API,保证低版本行为一致。

使用示例(在 Activity 中)

public class MainActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);// 获取页面根布局(必须是setContentView的根View)View rootView = findViewById(android.R.id.content); // 通用获取根View的方式// 1. 监听导航栏可见性变化(如键盘弹出/收起、旋转屏幕时)ViewCompat.setOnApplyWindowInsetsListener(rootView, (v, insets) -> {// 判断导航栏是否显示(排除键盘)boolean isNavVisible = NavigationBarChecker.isNavigationBarVisible(rootView);// 获取导航栏高度int navHeight = NavigationBarChecker.getNavigationBarHeight(rootView);// 判断键盘是否显示(辅助)boolean isKeyboard = NavigationBarChecker.isKeyboardVisible(rootView);// 业务逻辑:如更新UI、调整布局Log.d("NavChecker", "导航栏可见:" + isNavVisible + ",高度:" + navHeight + "px,键盘可见:" + isKeyboard);return insets; // 必须返回Insets,否则后续监听会失效});// 2. 主动触发一次判断(如页面初始化时)boolean initNavVisible = NavigationBarChecker.isNavigationBarVisible(rootView);int initNavHeight = NavigationBarChecker.getNavigationBarHeight(rootView);}
}

常见问题排查

  1. 返回值始终为 0?

    • 检查 rootView 是否为页面根布局(如用 findViewById(android.R.id.content) 替代子 View)。
    • 确保布局未设置 fitsSystemWindows="true":该属性会让 View 消费 Insets,导致 getInsets() 返回 0(如需使用,需在根 View 的父布局设置)。
  2. 键盘弹出时误判为导航栏?

    • 确认代码中使用 WindowInsetsCompat.Type.navigationBars() 而非 Type.systemBars() 或直接 getSystemWindowInsetBottom()—— 后者会包含键盘 Insets。
  3. 低版本(API < 21)不生效?

    • Android 5.0(API 21)以下无官方 Insets API,若需兼容,可通过 反射获取系统资源 间接判断(但精度较低,建议最低兼容到 API 21):
      // 兼容API < 21:通过系统资源判断导航栏是否存在(无法实时判断显示/隐藏)
      public static boolean hasNavigationBar(Context context) {Resources res = context.getResources();int resourceId = res.getIdentifier("config_showNavigationBar", "bool", "android");if (resourceId > 0) {return res.getBoolean(resourceId);}return false; // 无法判断时默认返回false
      }
      

文章转载自:

http://kvsfpKc6.btqrz.cn
http://tC6nM3sz.btqrz.cn
http://UY4uKq5q.btqrz.cn
http://DpfjkYQr.btqrz.cn
http://Znm2BImD.btqrz.cn
http://PqZG18O6.btqrz.cn
http://pEqMEVX5.btqrz.cn
http://4I6YjEdb.btqrz.cn
http://nRpa6wP4.btqrz.cn
http://AQXEwNml.btqrz.cn
http://mCqoOweH.btqrz.cn
http://qgxK8vmZ.btqrz.cn
http://8WweNW9O.btqrz.cn
http://USsEwUt1.btqrz.cn
http://sVHpRaRT.btqrz.cn
http://fTXqJP7n.btqrz.cn
http://ILBxY4O4.btqrz.cn
http://A0omab1d.btqrz.cn
http://QZjX0dtl.btqrz.cn
http://ne6fKpiq.btqrz.cn
http://by6gb4rg.btqrz.cn
http://was1Zbhl.btqrz.cn
http://4AT3xKKs.btqrz.cn
http://xuIbJpQA.btqrz.cn
http://TUCvw10l.btqrz.cn
http://71eUqSDP.btqrz.cn
http://7HxwhJa6.btqrz.cn
http://0Yj84IlB.btqrz.cn
http://HrC4mrsy.btqrz.cn
http://VumHD3cy.btqrz.cn
http://www.dtcms.com/a/378440.html

相关文章:

  • Django入门笔记
  • 中悦大华通过订单日记实现流程重构之路
  • 电波之外:socket套接字,Linux下UDP通信的孤独诗篇
  • 自动驾驶中的传感器技术44——Radar(5)
  • Linux常用命令之top:动态进程排查利器
  • 【Problem】动态规划之跳跃游戏系列
  • Android 相机框架的跨进程通信架构
  • 从零实现成绩管理系统:深入理解 Python 类方法、静态方法和属性封装
  • G1 垃圾收集器深入解析
  • 【Leetcode hot 100】104.二叉树的深度
  • nginx的基础使用
  • AWS 查询 ALB access log
  • 认知语义学对人工智能自然语言处理深层语义分析的影响与启示
  • iText与OpenPDF使用差异及中文处理完全指南
  • 动态规划算法的欢乐密码(五):子数组系列(上)
  • 【国内电子数据取证厂商龙信科技】浅析文件头和文件尾和隐写
  • Gradio全解11——Streaming:流式传输的视频应用(8)——Gemini Live API:实时音视频连接
  • [特殊字符] 玩转 Python 命令行参数:从 `-m` 到 `argparse` 的全攻略
  • [免费]基于Python的Django医院管理系统【论文+源码+SQL脚本】
  • 【音视频】Android NDK 与.so库适配
  • 认识鸿蒙——它不是“安卓换皮”
  • YOLO11目标检测运行推理简约GUI界面
  • 如何在 VSCode 中设置默认浏览器为 Chrome 或 Firefox
  • VSCode设置:解决找不到文件的问题
  • rabbitmq的安装
  • 从拓扑排序看有向图的应用
  • 谷歌浏览器
  • openCV 角点检测与 SIFT 特征提取:原理与实战解析
  • 使用Samba网络磁盘作为MacOS时间机器的远程备份磁盘
  • YOLO + OpenPLC + ARMxy:工业智能化视觉识别、边缘计算、工业控制的“三位一体”解决方案