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

发布于:2025-09-12 ⋅ 阅读:(20) ⋅ 点赞:(0)

在 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:指定只计算导航栏的Insets
        int 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 {

    @Override
    protected 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
      }
      

网站公告

今日签到

点亮在社区的每一天
去签到