【问题标题】:Check for navigation bar检查导航栏
【发布时间】:2013-04-12 03:03:26
【问题描述】:

我正在尝试检查加载时是否存在 android 导航栏,以便我可以相应地调整布局,有人有什么建议吗?

这是我要检测的导航栏:

附:到目前为止,我发现的只是尝试移除栏的“糟糕”方法,我不想这样做。

【问题讨论】:

  • 你为什么关心酒吧是否在那里?毕竟,屏幕尺寸本身可能与任何其他给定设备完全不同,无论条形如何。然后是 Kindle Fire 系列之类的设备,它们的栏不同。您不应该根据 UI 的可用空间而不是导航栏是否存在来做出决定吗?
  • 当在运行 4.1 的 S3 上进行测试时,导航栏不存在,而布局工作完美无缺,在运行 4.1 的 Nexus 4 上进行测试时,底部附近的一些布局是稍微切断。两个屏幕 1280x720 xhdpi
  • 当你的应用在另一个分辨率和密度的屏幕上运行时,你会得到不同的结果,与是否有导航栏无关。
  • 作为观察结果,没有系统导航栏的三星设备被列为“长”,而带有导航栏的 HTC 和摩托罗拉设备被列为“不长”——这将允许使用常规资源加载来确定不同的布局。但是,这并不严格匹配 long 与 notlong 的定义,这应该只是屏幕方面。原来,这是“可用的 dp 方面”(请参阅​​stackoverflow.com/a/17935955/91165 并仔细查看 Android 代码)。
  • @CommonsWare 我的用例涉及一个可以显示/隐藏 SystemNavBar 的沉浸式视频播放器。一个progressBar + SystemNavBar 显示onTap,并在另一个点击时消失。为了防止 SystemNav bar 与 progressBar 重叠,我需要为 SystemNavBar 的大小设置正确的边距,包括 SystemNavBar 不存在的情况。我举了一个例子,说明何时确定 SystemNavBar 是否存在是有用的。

标签: java android layout android-4.0-ice-cream-sandwich android-4.2-jelly-bean


【解决方案1】:

我花了一些时间,但我找到了一种比依赖 hasPermanentMenuKey() 更可靠的方法,它不适用于像 HTC One 这样具有没有菜单键,但有主页和返回键,所以不需要(或显示)软导航栏。要解决这个问题,请尝试以下代码来检查后退按钮:

boolean hasMenuKey = ViewConfiguration.get(context).hasPermanentMenuKey();
boolean hasBackKey = KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_BACK);

if(!hasMenuKey && !hasBackKey) {
    // Do whatever you need to do, this device has a navigation bar
}

【讨论】:

  • 在某些带有软键的设备上(如 Galaxy Tab 2)KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_BACK) 返回 true。
  • @zarej - 三星,你能做什么!
  • 但是如果菜单键不是导航栏的一部分,为什么还要检查呢?另外,检查KeyEvent.KEYCODE_HOME 不是明智之举吗?这可能会解决 Galaxy Tab 2 的问题。
  • 嗨,在 Lollipop hasBackKey 上,即使对于带有 NavBar 的设备也是如此 - 任何想法如何解决这个问题?
  • 这对我不起作用,Nexus5 为 hasMenuKey 和 hasBackKey 返回 true,因此它认为没有导航栏。 (Nexus7 为 hasMenuKey 和 hasBackKey 正确返回 false)。对我有用的更好的解决方案是:stackoverflow.com/a/29609679/2663916
【解决方案2】:

没有可靠的方法来检查导航栏。使用KeyCharacterMap.deviceHasKey,您可以检查设备上是否存在某些物理键,但此信息不是很有用,因为具有物理键的设备仍然可以具有导航栏。像 OnePlus One 这样的设备,或任何运行自定义 rom 的设备,在设置中都有一个禁用物理键的选项,并添加了一个导航栏。无法检查此选项是否启用,deviceHasKey 仍会为被此选项禁用的键返回 true。

这是你能得到的最接近的:

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

if (hasBackKey && hasHomeKey) {
    // no navigation bar, unless it is enabled in the settings
} else {
    // 99% sure there's a navigation bar
}

如果返回和主页按钮在设备上没有物理存在,则它必须有一个导航栏,否则用户将根本无法导航。但是,您永远无法 100% 确定这一点,因为制造商可能会错误地实施 deviceHasKey

【讨论】:

  • 这是否可以通过对上述编码的双重检查来实现按制造商ID?如果是这样,你如何处理它?
  • @jRhesk 通过制造商 ID 检查听起来不是一个好主意,但它可能会起作用。我不会在 Android SDK 中做这样的事情。
  • 我已经用 OnePlus 检查了上面的代码,即使我打开了底部的屏幕导航栏,它们的贵重物品也总是假的。这发生在 Oneplus A0001 上。我如何获得一个可以根据情况与不同供应商合作的工作?我觉得应该是外部sdk或者库来判断当前设备是否开启了屏幕导航菜单。
【解决方案3】:

另一种解决方案 (我班的一部分UtilsUISystem

    public static boolean hasNavBar (Resources resources)
    {
        //Emulator
        if (Build.FINGERPRINT.startsWith("generic"))
            return true;

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

【讨论】:

  • 这对我也不起作用,因为即使在没有导航栏的设备(例如三星设备)上也会为该资源返回值。对我有用的更好的解决方案是:stackoverflow.com/a/29609679/2663916
  • @se22as 这段代码对我有好处。我已经在 Galaxy S5 和很多 Nexus 上进行了测试。如果存在“config_showNavigationBar”标识符,则代码返回制造商设置的值。否则,这个标识符不存在,没有导航栏(pre-honeycomb)
【解决方案4】:

这是一个结合了 Pauland 和 Philask 解决方案的快速答案。不过,恐怕我没有足够的设备来测试它是否可以在任何地方工作。我很想听听其他人的结果。

boolean hasNavBar(Context context) {
    Resources resources = context.getResources();
    int id = resources.getIdentifier("config_showNavigationBar", "bool", "android");
    if (id > 0) {
        return resources.getBoolean(id);
    } else {    // Check for keys
        boolean hasMenuKey = ViewConfiguration.get(context).hasPermanentMenuKey();
        boolean hasBackKey = KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_BACK);
        return !hasMenuKey && !hasBackKey;
    }
}

【讨论】:

  • 我尝试使用它,不幸的是在 Nexus 5 上它仍然不正确。即使存在导航栏,“resources.getBoolean(id)”也会返回 false。
  • @se22as 您所说的 Nexus 5 是“真实”设备还是模拟器?我确实在 N5 Lollipop Android 模拟器上看到“config_showNavigationBar”返回 false。我希望这在现实世界中不是真的......
  • 我可以确认这确实在实际的 Nexus 5 设备上返回 true
【解决方案5】:

我已经这样做了,它适用于我测试的每台设备,甚至在模拟器上:

public static boolean hasNavigationBar(Activity activity) {
    Rect rectangle = new Rect();
    DisplayMetrics displayMetrics = new DisplayMetrics();
    activity.getWindow().getDecorView().getWindowVisibleDisplayFrame(rectangle);
    activity.getWindowManager().getDefaultDisplay().getRealMetrics(displayMetrics);
    return displayMetrics.heightPixels != (rectangle.top + rectangle.height());
}

【讨论】:

  • 最可靠的做法。因为它都是基于显示像素
【解决方案6】:

您可以将此代码添加到活动的 onCreate() 方法中:

 View decorView = getWindow().getDecorView();
decorView.setOnSystemUiVisibilityChangeListener
        (new View.OnSystemUiVisibilityChangeListener() {
            @Override
            public void onSystemUiVisibilityChange(int visibility) {
                if ((visibility & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0) {
                    // TODO: The navigation bar is visible. Make any desired
                    // adjustments to your UI, such as showing the action bar or
                    // other navigational controls.
                } else {
                    // TODO: The navigation bar is NOT visible. Make any desired
                    // adjustments to your UI, such as hiding the action bar or
                    // other navigational controls.
                }
            }
        });

【讨论】:

  • 这不适用于三星 Galaxy 5 桌子。该设备有一个硬件导航栏,即使栏不可能移动,这些回调仍然会进行。
【解决方案7】:

这个方法对我有用

    int id = getResources().getIdentifier("config_showNavigationBar","bool","android");
        boolean result = id > 0 && getResources().getBoolean(id);
//
        if(result) {

            // Do whatever you need to do, this device has a soft Navigation Bar
        }

它对我有用,并在许多设备上进行了测试。

【讨论】:

  • 不适用于我的设备之一 zuk z2 pro,但适用于我的 s8
  • Android P的自定义ROM或导航手势功能的任何解决方案?
  • 在 Grand Prime 上为我工作!
【解决方案8】:
boolean hasNavBar(Context context) {
    if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
        // navigation bar was introduced in Android 4.0 (API level 14)
        Resources resources = context.getResources();
        int id = resources.getIdentifier("config_showNavigationBar", "bool", "android");
        if (id > 0) {
            return resources.getBoolean(id);
        } else {    // Check for keys
            boolean hasMenuKey = ViewConfiguration.get(context).hasPermanentMenuKey();
            boolean hasBackKey = KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_BACK);
            return !hasMenuKey && !hasBackKey;
        }
    } else {
        return false;
    }
}

【讨论】:

    【解决方案9】:

    测量屏幕可能会更好。

    从 API 17 开始,有 getWindowManager().getDefaultDisplay().getRealSize(),可以与 getWindowManager().getDefaultDisplay().getSize() 返回的大小进行比较。

    如果你得到不同的结果,我认为可以肯定地说有一个导航栏,如果你得到相同的结果,就没有导航栏。需要注意的一件事是您的目标 SDK 和支持的屏幕,如果 Android 认为您的应用在当前设备上无法正常运行而不进行缩放,这可能会导致 getSize() 的结果被缩放。

    在 API 17 以下,您可以通过getWindowManager().getDefaultDisplay().getMetrics() 在横向和纵向模式下测量屏幕,同样,不同的结果可能意味着有一个导航栏。

    但是,如果您得到相同的结果,您实际上并不知道,因为即使在横向时,手机也可以将导航栏保持在较短的边缘。有根据的猜测是,如果宽度或高度比1280x8001280x7201024x600 等标准尺寸小 4% 到 8%,而另一个尺寸相同,那么可能还有一个导航酒吧。不过,不要赌它。分辨率太多,彼此之间的差异太小,无法正常工作。

    【讨论】:

    • 如果您从服务中尝试这种方法,如果您在全屏(即 youtube 或媒体播放器)中进行检查,您可能会得到错误的结果。只是一个注释。
    • 尽管这有副作用,但它很有意义并且似乎给出了正确的结果。 +1
    【解决方案10】:

    我看到上面的答案,我想指出 认为“不存在”可以看成高度为0; 所以它可以是这样的:

    public static int getScreenH(Context context) {
        DisplayMetrics dm = new DisplayMetrics();
        dm = context.getResources().getDisplayMetrics();
        int h = dm.heightPixels;
        return h;
    }
    
    public static int getDpi(Context context) {
    
        DisplayMetrics displayMetrics1 = context.getResources().getDisplayMetrics();
        int height1 = displayMetrics1.heightPixels;
    
        int dpi = 0;
        WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        Display display = windowManager.getDefaultDisplay();
        DisplayMetrics displayMetrics = new DisplayMetrics();
    
        @SuppressWarnings("rawtypes")
        Class c;
        try {
            c = Class.forName("android.view.Display");
            @SuppressWarnings("unchecked")
            Method method = c.getMethod("getRealMetrics", DisplayMetrics.class);
            method.invoke(display, displayMetrics);
            dpi = displayMetrics.heightPixels;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return dpi;
    }
    
    public static int getBottomStatusHeight(Context context) {
        int totalHeight = getDpi(context);
    
        int contentHeight = getScreenH(context);
    
        return totalHeight - contentHeight;
    }
    

    ```

    【讨论】:

      【解决方案11】:

      在 Android 10(API 级别 29)上,您还可以检查底部窗口插图:

      @RequiresApi(api = Build.VERSION_CODES.Q)
      private boolean hasNavigationBar() {
          final WindowInsets windowInsets = getWindow().getDecorView().getRootWindowInsets();
          if (windowInsets == null) {
                  throw new RuntimeException("Window is not attached");
          }
          return windowInsets.getTappableElementInsets().bottom > 0;
      }
      

      请注意,必须为getRootWindowInsets() 附加窗口才能返回非空值,因此您可能希望在onAttachedToWindow 中调用它。

      LineageOS 的启动器应用程序 Trebuchet (source) 也使用了此解决方案,这是我了解到的。

      【讨论】:

        【解决方案12】:

        解决方案:只有没有永久硬件密钥的设备才有导航栏,因此您可以检查 API 版本并使用 hasPermanentMenuKey() 查找硬件密钥

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

        【讨论】:

        • 正如其他解决方案所解释的那样,这非常不可靠 -> 没有菜单键,只有返回、主页和多任务键。
        猜你喜欢
        • 2013-11-29
        • 1970-01-01
        • 1970-01-01
        • 2022-09-23
        • 2017-07-08
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多