如何以编程方式获取Android导航栏的高度和宽度?

Android的屏幕底部的黑色导航栏不容易移动。 自3.0以来,它一直是Android的一部分,作为硬件按钮的替代品。 这是一张图片:

系统栏

我怎样才能得到这个UI元素的宽度和高度的像素的大小?

尝试下面的代码:

Resources resources = context.getResources(); int resourceId = resources.getIdentifier("navigation_bar_height", "dimen", "android"); if (resourceId > 0) { return resources.getDimensionPixelSize(resourceId); } return 0; 

我通过比较可用屏幕尺寸和实际屏幕尺寸来获得导航栏大小。 当应用程序可用的屏幕尺寸小于实际屏幕尺寸时,我假定导航栏存在。 然后我计算导航栏的大小。 此方法适用于API 14及更高版本。

 public static Point getNavigationBarSize(Context context) { Point appUsableSize = getAppUsableScreenSize(context); Point realScreenSize = getRealScreenSize(context); // navigation bar on the right if (appUsableSize.x < realScreenSize.x) { return new Point(realScreenSize.x - appUsableSize.x, appUsableSize.y); } // navigation bar at the bottom if (appUsableSize.y < realScreenSize.y) { return new Point(appUsableSize.x, realScreenSize.y - appUsableSize.y); } // navigation bar is not present return new Point(); } public static Point getAppUsableScreenSize(Context context) { WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); Display display = windowManager.getDefaultDisplay(); Point size = new Point(); display.getSize(size); return size; } public static Point getRealScreenSize(Context context) { WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); Display display = windowManager.getDefaultDisplay(); Point size = new Point(); if (Build.VERSION.SDK_INT >= 17) { display.getRealSize(size); } else if (Build.VERSION.SDK_INT >= 14) { try { size.x = (Integer) Display.class.getMethod("getRawWidth").invoke(display); size.y = (Integer) Display.class.getMethod("getRawHeight").invoke(display); } catch (IllegalAccessException e) {} catch (InvocationTargetException e) {} catch (NoSuchMethodException e) {} } return size; } 

NavigationBar的高度因某些设备而异,但也适用于某些方向。 首先,您必须检查设备是否有导航栏,然后如果设备是平板电脑或非平板电脑(手机),最后您必须查看设备的方向以获得正确的高度。

 public int getNavBarHeight(Context c) { int result = 0; boolean hasMenuKey = ViewConfiguration.get(c).hasPermanentMenuKey(); boolean hasBackKey = KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_BACK); if(!hasMenuKey && !hasBackKey) { //The device has a navigation bar Resources resources = c.getResources(); int orientation = resources.getConfiguration().orientation; int resourceId; if (isTablet(c)){ resourceId = resources.getIdentifier(orientation == Configuration.ORIENTATION_PORTRAIT ? "navigation_bar_height" : "navigation_bar_height_landscape", "dimen", "android"); } else { resourceId = resources.getIdentifier(orientation == Configuration.ORIENTATION_PORTRAIT ? "navigation_bar_height" : "navigation_bar_width", "dimen", "android"); } if (resourceId > 0) { return resources.getDimensionPixelSize(resourceId); } } return result; } private boolean isTablet(Context c) { return (c.getResources().getConfiguration().screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) >= Configuration.SCREENLAYOUT_SIZE_LARGE; } 

实际上,平板电脑上的导航栏(至少Nexus 7)在纵向和横向上的大小不同,所以此function应如下所示:

 private int getNavigationBarHeight(Context context, int orientation) { Resources resources = context.getResources(); int id = resources.getIdentifier( orientation == Configuration.ORIENTATION_PORTRAIT ? "navigation_bar_height" : "navigation_bar_height_landscape", "dimen", "android"); if (id > 0) { return resources.getDimensionPixelSize(id); } return 0; } 

我希望这可以帮助你

 public int getStatusBarHeight() { int result = 0; int resourceId = getResources().getIdentifier("status_bar_height", "dimen", "android"); if (resourceId > 0) { result = getResources().getDimensionPixelSize(resourceId); } return result; } public int getNavigationBarHeight() { boolean hasMenuKey = ViewConfiguration.get(context).hasPermanentMenuKey(); int resourceId = getResources().getIdentifier("navigation_bar_height", "dimen", "android"); if (resourceId > 0 && !hasMenuKey) { return getResources().getDimensionPixelSize(resourceId); } return 0; } 

这是我的代码添加paddingRight和paddingBottom到一个视图来闪避导航栏。 我在这里结合了一些答案,并与isInMultiWindowMode一起为横向定义了一个特殊的子句。 关键是阅读navigation_bar_height ,但也检查config_showNavigationBar ,以确保我们应该实际使用的高度。

以前的解决方案没有为我工作。 从Android 7.0开始,您必须考虑多窗口模式。 这将打破比较display.realSizedisplay.size的实现,因为realSize为您提供了整个屏幕的尺寸(两个拆分窗口), 尺寸仅为您提供了应用程序窗口的尺寸。 将填充设置为这种差异会使整个视图变为填充。

 /** Adds padding to a view to dodge the navigation bar. Unfortunately something like this needs to be done since there are no attr or dimens value available to get the navigation bar height (as of December 2016). */ public static void addNavigationBarPadding(Activity context, View v) { Resources resources = context.getResources(); if (hasNavigationBar(resources)) { int orientation = resources.getConfiguration().orientation; int size = getNavigationBarSize(resources); switch (orientation) { case Configuration.ORIENTATION_LANDSCAPE: if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && context.isInMultiWindowMode()) { break; } v.setPadding(v.getPaddingLeft(), v.getPaddingTop(), v.getPaddingRight() + size, v.getPaddingBottom()); break; case Configuration.ORIENTATION_PORTRAIT: v.setPadding(v.getPaddingLeft(), v.getPaddingTop(), v.getPaddingRight(), v.getPaddingBottom() + size); break; } } } private static int getNavigationBarSize(Resources resources) { int resourceId = resources.getIdentifier("navigation_bar_height", "dimen", "android"); return resourceId > 0 ? resources.getDimensionPixelSize(resourceId) : 0; } private static boolean hasNavigationBar(Resources resources) { int hasNavBarId = resources.getIdentifier("config_showNavigationBar", "bool", "android"); return hasNavBarId > 0 && resources.getBoolean(hasNavBarId); } 

底部导航栏的高度为48dp(在纵向和横向模式下),垂直放置时为42dp。

由Egidijus提出的解决方案,完美适用于Build.VERSION.SDK_INT> = 17

但是,在我的设备上执行以下语句Build.VERSION.SDK_INT <17时,出现“NoSuchMethodException”

 Display.class.getMethod("getRawHeight").invoke(display); 

我已经修改了方法getRealScreenSize()这种情况下:

 else if(Build.VERSION.SDK_INT >= 14) { View decorView = getActivity().getWindow().getDecorView(); size.x = decorView.getWidth(); size.y = decorView.getHeight(); } 

我解决了所有设备(包括Nexus 5,三星Galaxy Nexus 6 edge +,三星S10,三星Note II等)的这个问题。 我认为这将帮助您处理设备相关的问题。

这里我添加了两种types的代码,

Java代码(适用于Native Android):

 import android.content.Context; import android.content.res.Resources; import android.os.Build; import android.util.DisplayMetrics; import android.view.Display; import android.view.ViewConfiguration; import android.view.WindowManager; public class DeviceSpec { private int resourceID = -1; private Display display = null; private DisplayMetrics displayMetrics = null; private DisplayMetrics realDisplayMetrics = null; private Resources resources = null; private WindowManager windowManager = null; public double GetNavigationBarHeight(Context context) { try { windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); display = windowManager.getDefaultDisplay(); displayMetrics = new DisplayMetrics(); if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) { realDisplayMetrics = new DisplayMetrics(); display.getMetrics(displayMetrics); display.getRealMetrics(realDisplayMetrics); if(displayMetrics.heightPixels != realDisplayMetrics.heightPixels) { resources = context.getResources(); return GetNavigationBarSize(context); } } else { resources = context.getResources(); resourceID = resources.getIdentifier("config_showNavigationBar", "bool", "android"); if (resourceID > 0 && resources.getBoolean(resourceID)) return GetNavigationBarSize(context); } } catch (Exception e){ e.printStackTrace(); } return 0; } private double GetNavigationBarSize(Context context) { resourceID = resources.getIdentifier("navigation_bar_height", "dimen", "android"); if (resourceID > 0 && ViewConfiguration.get(context).hasPermanentMenuKey()) return (resources.getDimensionPixelSize(resourceID) / displayMetrics.density); return 0; } } 

和C#代码(用于Xamarin Forms / Android)

 int resourceId = -1; IWindowManager windowManager = null; Display defaultDisplay = null; DisplayMetrics displayMatrics = null; DisplayMetrics realMatrics = null; Resources resources = null; public double NavigationBarHeight { get { try { windowManager = Forms.Context.GetSystemService(Context.WindowService).JavaCast(); defaultDisplay = windowManager.DefaultDisplay; displayMatrics = new DisplayMetrics(); if (Build.VERSION.SdkInt >= BuildVersionCodes.JellyBeanMr2) { realMatrics = new DisplayMetrics(); defaultDisplay.GetMetrics(displayMatrics); defaultDisplay.GetRealMetrics(realMatrics); if (displayMatrics.HeightPixels != realMatrics.HeightPixels) { resources = Forms.Context.Resources; return GetHeightOfNivigationBar(); } } else { resources = Forms.Context.Resources; resourceId = resources.GetIdentifier("config_showNavigationBar", "bool", "android"); if (resourceId > 0 && resources.GetBoolean(resourceId)) return GetHeightOfNivigationBar(); } } catch (Exception e) { } return 0; } } private double GetHeightOfNivigationBar() { resourceId = resources.GetIdentifier("navigation_bar_height", "dimen", "android"); if (!ViewConfiguration.Get(Forms.Context).HasPermanentMenuKey && resourceId > 0) { return resources.GetDimensionPixelSize(resourceId) / displayMatrics.Density; } return 0; } 

结合@egis和其他人的回答 – 这适用于各种设备,在Pixel EMU,Samsung S6,Sony Z3,Nexus 4上进行测试。此代码使用显示尺寸来测试导航栏的可用性,然后使用实际系统导航栏大小,如果存在。

 /** * Calculates the system navigation bar size. */ public final class NavigationBarSize { private final int systemNavBarHeight; @NonNull private final Point navBarSize; public NavigationBarSize(@NonNull Context context) { Resources resources = context.getResources(); int displayOrientation = resources.getConfiguration().orientation; final String name; switch (displayOrientation) { case Configuration.ORIENTATION_PORTRAIT: name = "navigation_bar_height"; break; default: name = "navigation_bar_height_landscape"; } int id = resources.getIdentifier(name, "dimen", "android"); systemNavBarHeight = id > 0 ? resources.getDimensionPixelSize(id) : 0; navBarSize = getNavigationBarSize(context); } public void adjustBottomPadding(@NonNull View view, @DimenRes int defaultHeight) { int height = 0; if (navBarSize.y > 0) { // the device has a nav bar, get the correct size from the system height = systemNavBarHeight; } if (height == 0) { // fallback to default height = view.getContext().getResources().getDimensionPixelSize(defaultHeight); } view.setPadding(0, 0, 0, height); } @NonNull private static Point getNavigationBarSize(@NonNull Context context) { Point appUsableSize = new Point(); Point realScreenSize = new Point(); WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); if (windowManager != null) { Display display = windowManager.getDefaultDisplay(); display.getSize(appUsableSize); display.getRealSize(realScreenSize); } return new Point(realScreenSize.x - appUsableSize.x, realScreenSize.y - appUsableSize.y); } } 

这是我如何解决这个问题。 我做了一个需要填充的可隐藏底栏,这取决于是否有导航条(电容,屏幕上或只是棒棒糖)。


视图

 setPadding(0, 0, 0, Utils.hasNavBar(getContext()) ? 30 : 0); 

Utils.java

 public static boolean hasNavBar(Context context) { // Kitkat and less shows container above nav bar if (android.os.Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) { return false; } // Emulator if (Build.FINGERPRINT.startsWith("generic")) { return true; } boolean hasMenuKey = ViewConfiguration.get(context).hasPermanentMenuKey(); boolean hasBackKey = KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_BACK); boolean hasNoCapacitiveKeys = !hasMenuKey && !hasBackKey; Resources resources = context.getResources(); int id = resources.getIdentifier("config_showNavigationBar", "bool", "android"); boolean hasOnScreenNavBar = id > 0 && resources.getBoolean(id); return hasOnScreenNavBar || hasNoCapacitiveKeys || getNavigationBarHeight(context, true) > 0; } public static int getNavigationBarHeight(Context context, boolean skipRequirement) { int resourceId = context.getResources().getIdentifier("navigation_bar_height", "dimen", "android"); if (resourceId > 0 && (skipRequirement || hasNavBar(context))) { return context.getResources().getDimensionPixelSize(resourceId); } return 0; } 

在我的情况下,我想有这样的事情:

截图

我不得不按照@Mdlc所建议的做同样的事情,但可能稍微简单一点( 针对> = 21):

  //kotlin val windowManager = getSystemService(Context.WINDOW_SERVICE) as WindowManager val realSize = Point() windowManager.defaultDisplay.getRealSize(realSize); val usableRect = Rect() windowManager.defaultDisplay.getRectSize(usableRect) Toast.makeText(this, "Usable Screen: " + usableRect + " real:"+realSize, Toast.LENGTH_LONG).show() window.decorView.setPadding(usableRect.left, usableRect.top, realSize.x - usableRect.right, realSize.y - usableRect.bottom) 

它也适用于景观:

景观

编辑上面的解决方案在多窗口模式下不能正常工作,其中可用矩形不仅仅是由于导航栏而是由于自定义窗口大小而变小。 我注意到的一件事是,在多窗口的导航栏并没有hover在应用程序,所以即使没有更改DecorView填充我们有正确的行为:

不改变装饰视图填充的多窗口 单一窗口不改变装饰视图填充

请注意导航栏如何在应用程序底部hover的情况。 幸运的是,这很容易解决。 我们可以检查应用程序是否是多窗口。 下面的代码还包括计算和调整工具栏位置的部分(完整的解决方案: https : //stackoverflow.com/a/14213035/477790 )

  // kotlin // Let the window flow into where window decorations are window.addFlags(WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN) window.addFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS) // calculate where the bottom of the page should end up, considering the navigation bar (back buttons, ...) val windowManager = getSystemService(Context.WINDOW_SERVICE) as WindowManager val realSize = Point() windowManager.defaultDisplay.getRealSize(realSize); val usableRect = Rect() windowManager.defaultDisplay.getRectSize(usableRect) Toast.makeText(this, "Usable Screen: " + usableRect + " real:" + realSize, Toast.LENGTH_LONG).show() if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N || !isInMultiWindowMode) { window.decorView.setPadding(usableRect.left, usableRect.top, realSize.x - usableRect.right, realSize.y - usableRect.bottom) // move toolbar/appbar further down to where it should be and not to overlap with status bar val layoutParams = ConstraintLayout.LayoutParams(appBarLayout.layoutParams as ConstraintLayout.LayoutParams) layoutParams.topMargin = getSystemSize(Constants.statusBarHeightKey) appBarLayout.layoutParams = layoutParams } 

三星popup模式下的结果:

在这里输入图像说明