如何在Android程序中以编程方式检测底部软导航栏是否可用?

44

我正在尝试通过Android程序确定软导航栏。我没有找到直接的方法来确定。是否有任何方式可以确定导航栏的可用性。

软导航栏图像在此处。

输入图像描述

8个回答

78

以下方法对我有效,并在许多设备上进行了测试。

public boolean hasNavBar (Resources resources)
    {
        int id = resources.getIdentifier("config_showNavigationBar", "bool", "android");
        return id > 0 && resources.getBoolean(id);
    }

注意:在实际设备上验证该方法


1
这段代码可以运行,但是请使用'"android"'代替'android'。 - MattButtMatt
12
两种情况下均返回“false”。 - CoolMind
4
иҜ·жіЁж„ҸпјҢжӯӨж–№жі•еңЁAndroidжЁЎжӢҹеҷЁдёҠе§Ӣз»Ҳиҝ”еӣһfalseгҖӮжңүе…іиҜҰз»Ҷи§ЈйҮҠпјҢиҜ·еҸӮйҳ…жӯӨжҸҗдәӨж¶ҲжҒҜгҖӮ - Sam
4
尽管在模拟器上无法工作,但其与Android源代码非常接近,因此在真实设备上应该相当准确。 - Sam
在一加3中进行检查,启用两者总是返回false。 - Muneef M
显示剩余3条评论

27

据我所知,您可以通过以下方式检测它:

boolean hasSoftKey = ViewConfiguration.get(context).hasPermanentMenuKey();

但是它需要API 14+


如果上述解决方法对您不起作用,请尝试下面的方法

public boolean isNavigationBarAvailable(){

        boolean hasBackKey = KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_BACK);
        boolean hasHomeKey = KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_HOME);

        return (!(hasBackKey && hasHomeKey));
    }

感谢您的回复。我想确定软导航栏是否存在。 - Raghu Mudem
非常感谢您提供如此出色的信息,由于这类设备的存在,您可以尝试我的第二个解决方案(请查看更新后的答案)。 - IshRoid
不知道为什么Sony Xperia Z3上hasBackKey等于true。这导致第二个解决方案失败。 - Hamzeh Soboh
谢谢兄弟,你救了我的命! - PriyankaChauhan
我在三星A70和一些华为设备上进行了测试。这个解决方案是不正确的,该方法总是返回true。我的解决方案基于当前屏幕上显示的导航栏高度来检测它是否可见。https://dev59.com/omIj5IYBdhLWcg3wb0rX#59302624 - Anh Duy
显示剩余5条评论

17

虽然这是一种取巧的方法,但效果还不错。试试看吧。

public static boolean hasSoftKeys(WindowManager windowManager){
  Display d = windowManager.getDefaultDisplay();

  DisplayMetrics realDisplayMetrics = new DisplayMetrics();
  d.getRealMetrics(realDisplayMetrics);  

  int realHeight = realDisplayMetrics.heightPixels;
  int realWidth = realDisplayMetrics.widthPixels;

  DisplayMetrics displayMetrics = new DisplayMetrics();
  d.getMetrics(displayMetrics);

  int displayHeight = displayMetrics.heightPixels;
  int displayWidth = displayMetrics.widthPixels;

  return (realWidth - displayWidth) > 0 || (realHeight - displayHeight) > 0;
}

同意这是一个hack,但是非常棒。赞! - Saksham Khurana
2
这可能在新的华为 P20 和其他带有顶部凹口的设备上无法正常工作。 - SpaceBison

10

接受的答案在大多数真实设备上应该能正常工作,但在模拟器上无法正常工作。

然而,在Android 4.0及以上版本中,存在一个内部API,也适用于模拟器:IWindowManager.hasNavigationBar()。您可以使用反射来访问它:

/**
 * Returns {@code null} if this couldn't be determined.
 */
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
@SuppressLint("PrivateApi")
public static Boolean hasNavigationBar() {
    try {
        Class<?> serviceManager = Class.forName("android.os.ServiceManager");
        IBinder serviceBinder = (IBinder)serviceManager.getMethod("getService", String.class).invoke(serviceManager, "window");
        Class<?> stub = Class.forName("android.view.IWindowManager$Stub");
        Object windowManagerService = stub.getMethod("asInterface", IBinder.class).invoke(stub, serviceBinder);
        Method hasNavigationBar = windowManagerService.getClass().getMethod("hasNavigationBar");
        return (boolean)hasNavigationBar.invoke(windowManagerService);
    } catch (ClassNotFoundException | ClassCastException | NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
        Log.w("YOUR_TAG_HERE", "Couldn't determine whether the device has a navigation bar", e);
        return null;
    }
}

当然,使用内部API有点不舒服,但嘿,它可以在我所有的设备上工作(至少支持到API25)。额外加分是它不依赖于Context。 - weaknespase
它对我有效,但是我收到了一个关于可能会出现空指针异常的警告。使用它安全吗? - JavierSegoviaCordoba
@Dahnark 这是一个内部 API,所以不能保证它在特定设备上能够正常工作。我示例中的方法返回 null,因此您可以检测它是否起作用。当发生这种情况时,您需要决定该怎么做并添加相应的代码。 - Sam
@Sam 我知道,但有没有可能避免这个警告?我已经使用 if-else 处理了三种情况(有导航、没有导航、null),但仍然收到警告。 - JavierSegoviaCordoba
@Dahnark 哦,我明白了。不知道,抱歉。 - Sam
在Android P中有没有一种方法可以获取通知栏是否可见? - Nik

4

尝试使用此方法,您可以检测导航栏是否存在。

@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
public boolean hasNavBar(Context context) {
    Point realSize = new Point();
    Point screenSize = new Point();
    boolean hasNavBar = false;
    DisplayMetrics metrics = new DisplayMetrics();
    this.getWindowManager().getDefaultDisplay().getRealMetrics(metrics);
    realSize.x = metrics.widthPixels;
    realSize.y = metrics.heightPixels;
    getWindowManager().getDefaultDisplay().getSize(screenSize);
    if (realSize.y != screenSize.y) {
        int difference = realSize.y - screenSize.y;
        int navBarHeight = 0;
        Resources resources = context.getResources();
        int resourceId = resources.getIdentifier("navigation_bar_height", "dimen", "android");
        if (resourceId > 0) {
            navBarHeight = resources.getDimensionPixelSize(resourceId);
        }
        if (navBarHeight != 0) {
            if (difference == navBarHeight) {
                hasNavBar = true;
            }
        }

    }
    return hasNavBar;

}

干得好!但你需要稍微调整一下代码,因为这只适用于纵向模式,但设备也可能处于横向方向。 - Gerrit Humberg

2

正确答案和其他的现在都不是实际的。
有一些选项,比如“全屏显示 -> 全屏手势”,其中导航栏被隐藏,但所有这些方法都会返回它是存在的。

我建议您使用以下方法来检查系统视图的大小。 在onCreate方法中:

最初的回答:

目前正确的答案和其他方法已经过时。有一些选项可以隐藏导航栏,例如“全屏显示 -> 全屏手势”,但所有这些方法都会返回导航栏存在。

我建议您使用以下方法来检查系统视图的大小。 在onCreate方法中:

ViewCompat.setOnApplyWindowInsetsListener(findViewById(android.R.id.content), 
  (v, insets) -> { 
      int navigationBarHeight = insets.getSystemWindowInsetBottom();
      return insets;
  });

1
找到软导航是否启用的目的是确定提供的窗口大小,并根据其设置布局。
有一个非常强大的工具叫做“decorView”,可以从之前设置窗口,以便直接在其下实现方法。它可以写成这样:
val decorView = window.decorView
decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE

在框架内开始编写您想要创建的任何方法。

在尝试了大量的方法和技巧后,发现这个方法是唯一有效的。


1
其他答案对我没有帮助。但是知道导航栏是否显示是非常有用的,特别是在Android P/Q之后,用户可以将其滑出屏幕。我遇到了这篇文章https://blog.stylingandroid.com/gesture-navigation-window-insets/,并制作了这样的方法。
fun hasNavBar(activity: Activity): Boolean {
    val temporaryHidden = activity.window.decorView.visibility and View.SYSTEM_UI_FLAG_HIDE_NAVIGATION != 0
    if (temporaryHidden) return false
    val decorView = activity.window.decorView
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        decorView.rootWindowInsets?.let{
            return it.stableInsetBottom != 0
        }
    }
    return true
}

请编辑您的答案以解释您提供的代码。在您的答案中直接放置代码的解释将有助于未来的读者理解它的作用和方式。 - mypetlion
我的回答哪里不清楚了吗?第一部分是检查活动窗口decorView的HIDE_NAVIGATION ui可见性标志,第二部分是检查这些decorView的底部插入。适用于23 API及以上版本。 - parmakli
没有特别不清楚的地方,但通常鼓励在本站对代码片段进行注释或解释。 OP提出问题是因为他们不知道如何解决问题,这可能意味着他们从未看过你代码中的某些或所有个别语句(如果他们之前看过,他们可能已经自己解决了问题)。 因此,逐个添加代码块的注释是有价值的。解释可以放在答案本身而不是评论中,请注意。 - mypetlion

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接