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

Posted

技术标签:

【中文标题】如何以编程方式获取 Android 导航栏的高度和宽度?【英文标题】:How do I get the height and width of the Android Navigation Bar programmatically? 【发布时间】:2013-12-14 09:09:35 【问题描述】:

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

如何获取此 UI 元素的宽度和高度(以像素为单位)?

【问题讨论】:

这里添加了这个问题的最佳解决方案。我们可以通过比较显示指标和真实指标来识别设备中是否存在导航栏。看看我的回答,我添加了完整的代码来找出整个 android 设备的实际导航栏大小。 【参考方案1】:

试试下面的代码:

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

return 0;

【讨论】:

谢谢。 +1。你知道这种方法有多稳定吗?资源标识符是否会在不同平台版本之间发生变化? 请注意,这段代码在没有导航栏的设备上不会返回 0。在三星 S2 和 S3 上测试。我得到了 72 和 96。 @Egidijus 看看我的回答,对于有物理导航的设备,它将返回 0 ***.com/a/29938139/1683141 这对我不起作用。对于屏幕上没有导航栏的设备,我仍然会返回一个值。我的测试显示此代码中返回的 v 值为 = Nexus5:144 |连结7:75 |三星S10:96 |三星NoteII:96。三星没有导航栏,因此使用此代码会让您认为它确实有。 此代码显示导航栏的默认大小(也可以尝试navigation_bar_height_landscape 用于横向模式下的导航栏高度,navigation_bar_width 用于垂直导航的宽度酒吧)。您必须单独找出导航栏是否以及实际显示的位置,例如通过测试物理菜单按钮的存在。也许您可以在android.googlesource.com/platform/frameworks/base/+/…的Android源代码中找到其他方式@【参考方案2】:

我通过将应用程序可用的屏幕尺寸与实际屏幕尺寸进行比较来获得导航栏尺寸。我假设当应用程序可用的屏幕尺寸小于实际屏幕尺寸时,导航栏会出现。然后我计算导航栏的大小。此方法适用于 API 14 及更高版本。

public static Point getNavigationBarSize(Context context) 
    Point appUsableSize = getAppUsableScreenSize(context);
    Point realScreenSize = getRealScreenSize(context);

    // navigation bar on the side
    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;

更新

如需考虑显示屏切口的解决方案,请查看John's answer。

【讨论】:

这是唯一适用于我测试过的所有设备差异的解决方案。我的要求是在考虑方向的情况下确定导航栏是否位于屏幕底部。我稍微调整了答案中的代码,只返回屏幕底部导航栏的大小,0 表示它不存在。我的测试表明 Nexus5 = Portrait:144, Landscape:0 | Nexus7 = 纵向:96,横向:96 |三星 Note II = 纵向:0,横向:0 |三星 S10 = 纵向:0,横向:0 我还可以确认 Danylo 的解决方案仅适用于某些型号。 Egidijus 的这个解决方案是一个更好的工作解决方案,并且适用于迄今为止测试的所有模型。谢谢你。 状态栏是应用可用屏幕的一部分。 这可行,但不考虑现有栏是否隐藏。知道如何检查吗? 这也不考虑DisplayCutout【参考方案3】:

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;

【讨论】:

这对我不起作用,Nexus5 为 hasMenuKey 和 hasBackKey 返回 true,因此它认为没有导航栏。 (Nexus7 为 hasMenuKey 和 hasBackKey 正确返回 false) 我已经在 Nexus 5 上测试了这段代码,它对两者都返回 false。你确定你不在模拟器或ROM上? 是的,我使用的是模拟器,对不起,我应该在之前的声明中澄清一下 不应该是 !hasMenuKey || !hasBackKey 而不是 !hasMenuKey && !hasBackKey ?请验证 不幸的是,它在 Sony Xperia Z3 hasBackKey = false! 上不起作用,尽管它应该是真的。以下方法改为:boolean navBarExists = getResources().getBoolean(getResources().getIdentifier("config_showNavigationBar", "bool", "android"));【参考方案4】:

实际上平板电脑(至少 Nexus 7)上的导航栏在纵向和横向上有不同的大小,所以这个功能应该是这样的:

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;

【讨论】:

要从上下文中检索方向,请使用context.getResources().getConfiguration().orientation 这里添加了这个问题的最佳解决方案。我们可以通过比较显示指标和真实指标来识别设备中是否存在导航栏。看看我的回答,我添加了完整的代码来找出整个 android 设备的实际导航栏大小。【参考方案5】:

我认为这里有更好的答案,因为它也可以让您获得均匀的切口高度。

获取您的根视图,并添加 setOnApplyWindowInsetsListener(或者您可以从它覆盖 onApplyWindowInsets),并从中获取插图。

在我的相机活动中,我将等于 systemBars.bottom 的填充添加到我的底部布局中。最后,它修复了剪切问题。

使用 appcompat 是这样的

ViewCompat.setOnApplyWindowInsetsListener(binding.root)  v, insets ->
    val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
    binding.takePictureLayout.apply 
        setPaddingRelative(paddingStart, paddingTop, paddingEnd, systemBars.bottom)
    
    return@setOnApplyWindowInsetsListener insets

没有 appcompat,这个:

mCameraSourcePreview.setOnApplyWindowInsetsListener((v, insets) ->  ... )

【讨论】:

可以改用setOnApplyWindowInsetsListener吗?如果是这样,怎么做?你为什么要区分 LOLLIPOP 和其他? 是的,有可能,而且更好。我会修复答案。和区分也不需要 我明白了。谢谢! 这是 API >= 20 的正确方法。请参阅 video 的 21:23 当我从 onCreate() 调用它时,监听器永远不会被执行。我错过了什么吗?【参考方案6】:

希望对你有帮助

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;

【讨论】:

【参考方案7】:

这是我将 paddingRight 和 paddingBottom 添加到 View 以避开导航栏的代码。我在这里结合了一些答案,并与isInMultiWindowMode一起为横向制作了一个特殊的子句。关键是要阅读 navigation_bar_height,还要检查 config_showNavigationBar 以确保我们应该实际使用高度。

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

/** 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);

【讨论】:

【参考方案8】:

2021年新答案来救场


来自Egis's 答案:

context.navigationBarHeight

扩展 getter 在哪里

val Context.navigationBarHeight: Int
get() 
    val windowManager = getSystemService(Context.WINDOW_SERVICE) as WindowManager

    return if (Build.VERSION.SDK_INT >= 30) 
        windowManager
                .currentWindowMetrics
                .windowInsets
                .getInsets(WindowInsets.Type.navigationBars())
                .bottom

     else 
        val currentDisplay = try 
            display
         catch (e: NoSuchMethodError) 
            windowManager.defaultDisplay
        

        val appUsableSize = Point()
        val realScreenSize = Point()
        currentDisplay?.apply 
            getSize(appUsableSize)
            getRealSize(realScreenSize)
        

        // navigation bar on the side
        if (appUsableSize.x < realScreenSize.x) 
            return realScreenSize.x - appUsableSize.x
        

        // navigation bar at the bottom
        return if (appUsableSize.y < realScreenSize.y) 
            realScreenSize.y - appUsableSize.y
         else 0
    

测试于:

带有导航栏的模拟器 像素 3a (api 30) 像素 2 (api 28) 像素 3 (api 25) 像素 2 (api 21) 小米 Poco f2 pro 带和不带导航栏(全屏)

【讨论】:

你用的是什么 compile sdk?我没有找到一些方法 @jboxxpradhana 目标和编译 SDK 版本 30 它总是返回导航+状态。【参考方案9】:

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

但是在我的设备上使用 Build.VERSION.SDK_INT

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();

【讨论】:

【参考方案10】:

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

这里我加了两种代码,

Java 代码(用于原生 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<IWindowManager>();
                    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;
        

【讨论】:

Display.getRealMetrics() 需要 API 级别 17 如果 (Build.VERSION.SdkInt >= BuildVersionCodes.JellyBeanMr2) 已检查。 代码需要修复。我的 LG Nexus 5X 将获得 0.0,原因如下: [1] 当 screenOrientation 是默认值时,hasPermanentMenuKeyfalse 只有在不旋转的情况下。 [2]screenOrientation为横向时,属于displayMetrics.heightPixels != realDisplayMetrics.heightPixels)的else。 [3]screenOrientation为纵向时,hasPermanentMenuKeyfalse 它有效,但在 4.x 上如果(hasPermanentMenuKey)是浪费,请评论它 您的条件应该是 if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1)。需要 API 17 才能执行 public void getRealMetrics (DisplayMetrics outMetrics)。请参阅文档:developer.android.com/reference/kotlin/android/util/…【参考方案11】:

如何获取导航栏和状态栏的高度。此代码适用于某些华为设备和三星设备。 Egis 上面的解决方案很好,但是在某些设备上仍然不正确。所以,我改进了它。

这是获取状态栏高度的代码

private fun getStatusBarHeight(resources: Resources): Int 
        var result = 0
        val resourceId = resources.getIdentifier("status_bar_height", "dimen", "android")
        if (resourceId > 0) 
            result = resources.getDimensionPixelSize(resourceId)
        
        return result
    

即使导航栏被隐藏,该方法也总是返回导航栏的高度。

private fun getNavigationBarHeight(resources: Resources): Int 
    val resourceId = resources.getIdentifier("navigation_bar_height", "dimen", "android")
    return if (resourceId > 0) 
        resources.getDimensionPixelSize(resourceId)
     else 0

注意:在三星 A70 上,此方法返回状态栏高度 + 导航栏高度。 在其他设备(华为)上,它只返回导航栏的高度,隐藏导航栏时返回0。

private fun getNavigationBarHeight(): Int 
        val display = activity?.windowManager?.defaultDisplay
        return if (display == null) 
            0
         else 
            val realMetrics = DisplayMetrics()
            display.getRealMetrics(realMetrics)
            val metrics = DisplayMetrics()
            display.getMetrics(metrics)
            realMetrics.heightPixels - metrics.heightPixels
        
    

这是获取导航栏和状态栏高度的代码

val metrics = DisplayMetrics()
        activity?.windowManager?.defaultDisplay?.getRealMetrics(metrics)

        //resources is got from activity

        //NOTE: on SamSung A70, this height = height of status bar + height of Navigation bar
        //On other devices (Huawei), this height = height of Navigation bar
        val navigationBarHeightOrNavigationBarPlusStatusBarHeight = getNavigationBarHeight()

        val statusBarHeight = getStatusBarHeight(resources)
        //The method will always return the height of navigation bar even when the navigation bar was hidden.
        val realNavigationBarHeight = getNavigationBarHeight(resources)

        val realHeightOfStatusBarAndNavigationBar =
                if (navigationBarHeightOrNavigationBarPlusStatusBarHeight == 0 || navigationBarHeightOrNavigationBarPlusStatusBarHeight < statusBarHeight) 
                    //Huawei: navigation bar is hidden
                    statusBarHeight
                 else if (navigationBarHeightOrNavigationBarPlusStatusBarHeight == realNavigationBarHeight) 
                    //Huawei: navigation bar is visible
                    statusBarHeight + realNavigationBarHeight
                 else if (navigationBarHeightOrNavigationBarPlusStatusBarHeight < realNavigationBarHeight) 
                    //SamSung A70: navigation bar is still visible but it only displays as a under line
                    //navigationBarHeightOrNavigationBarPlusStatusBarHeight = navigationBarHeight'(under line) + statusBarHeight
                    navigationBarHeightOrNavigationBarPlusStatusBarHeight
                 else 
                    //SamSung A70: navigation bar is visible
                    //navigationBarHeightOrNavigationBarPlusStatusBarHeight == statusBarHeight + realNavigationBarHeight
                    navigationBarHeightOrNavigationBarPlusStatusBarHeight
                

【讨论】:

【参考方案12】:

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

// Return the NavigationBar height in pixels if it is present, otherwise return 0
public static int getNavigationBarHeight(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());

【讨论】:

【参考方案13】:

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

【讨论】:

高度通常为 48dp。但是一些制造商可以改变它,一些定制的 ROM 可以改变它,等等。最好依靠 Danylo 的回答 (***.com/a/26118045/1377145) 来获得确切的大小 那就更好了。谢谢。 垂直放置?比如在横向模式下?【参考方案14】:

结合@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);
	

【讨论】:

如果我想以编程方式将 TextView 的高度设置为屏幕可用高度的底部,我将如何使用这个类?【参考方案15】:

获取导航栏高度的测试代码(以像素为单位):

public static int getNavBarHeight(Context c) 
    int resourceId = c.getResources()
                      .getIdentifier("navigation_bar_height", "dimen", "android");
    if (resourceId > 0) 
        return c.getResources().getDimensionPixelSize(resourceId);
    
    return 0;

获取状态栏高度的测试代码(以像素为单位):

public static int getStatusBarHeight(Context c) 
    int resourceId = c.getResources()
                      .getIdentifier("status_bar_height", "dimen", "android");
    if (resourceId > 0) 
        return c.getResources().getDimensionPixelSize(resourceId);
    
    return 0;

将像素转换为 dp:

public static int pxToDp(int px) 
    return (int) (px / Resources.getSystem().getDisplayMetrics().density);

【讨论】:

【参考方案16】:

这是我解决这个问题的方法。我制作了一个可隐藏的底栏,需要根据是否有导航栏(电容式、屏幕上或只是棒棒糖前)进行填充。


查看

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;

【讨论】:

【参考方案17】:

就我而言,我想要这样的东西:

我必须按照@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)

它也适用于横向:

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

请注意在这些场景中导航栏悬停在应用底部的方式之间的区别。 幸运的是,这很容易解决。我们可以检查应用程序是否是多窗口。下面的代码还包括计算和调整工具栏位置的部分(完整解决方案:https://***.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
    

三星弹出模式的结果:

【讨论】:

【参考方案18】:

对于三星 S8,以上提供的方法都没有提供适当的导航栏高度,因此我使用了 KeyboardHeightProvider keyboard height provider android。它给了我负值的高度,对于我的布局定位,我在计算中调整了这个值。

这里是KeyboardHeightProvider.java

import android.app.Activity;
import android.content.res.Configuration;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.drawable.ColorDrawable;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
import android.view.WindowManager.LayoutParams;
import android.widget.PopupWindow;


/**
 * The keyboard height provider, this class uses a PopupWindow
 * to calculate the window height when the floating keyboard is opened and closed. 
 */
public class KeyboardHeightProvider extends PopupWindow 

    /** The tag for logging purposes */
    private final static String TAG = "sample_KeyboardHeightProvider";

    /** The keyboard height observer */
    private KeyboardHeightObserver observer;

    /** The cached landscape height of the keyboard */
    private int keyboardLandscapeHeight;

    /** The cached portrait height of the keyboard */
    private int keyboardPortraitHeight;

    /** The view that is used to calculate the keyboard height */
    private View popupView;

    /** The parent view */
    private View parentView;

    /** The root activity that uses this KeyboardHeightProvider */
    private Activity activity;

    /** 
     * Construct a new KeyboardHeightProvider
     * 
     * @param activity The parent activity
     */
    public KeyboardHeightProvider(Activity activity) 
        super(activity);
        this.activity = activity;

        LayoutInflater inflator = (LayoutInflater) activity.getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
        this.popupView = inflator.inflate(R.layout.popupwindow, null, false);
        setContentView(popupView);

        setSoftInputMode(LayoutParams.SOFT_INPUT_ADJUST_RESIZE | LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
        setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED);

        parentView = activity.findViewById(android.R.id.content);

        setWidth(0);
        setHeight(LayoutParams.MATCH_PARENT);

        popupView.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() 

                @Override
                public void onGlobalLayout() 
                    if (popupView != null) 
                        handleOnGlobalLayout();
                    
                
            );
    

    /**
     * Start the KeyboardHeightProvider, this must be called after the onResume of the Activity.
     * PopupWindows are not allowed to be registered before the onResume has finished
     * of the Activity.
     */
    public void start() 

        if (!isShowing() && parentView.getWindowToken() != null) 
            setBackgroundDrawable(new ColorDrawable(0));
            showAtLocation(parentView, Gravity.NO_GRAVITY, 0, 0);
        
    

    /**
     * Close the keyboard height provider, 
     * this provider will not be used anymore.
     */
    public void close() 
        this.observer = null;
        dismiss();
    

    /** 
     * Set the keyboard height observer to this provider. The 
     * observer will be notified when the keyboard height has changed. 
     * For example when the keyboard is opened or closed.
     * 
     * @param observer The observer to be added to this provider.
     */
    public void setKeyboardHeightObserver(KeyboardHeightObserver observer) 
        this.observer = observer;
    

    /**
     * Get the screen orientation
     *
     * @return the screen orientation
     */
    private int getScreenOrientation() 
        return activity.getResources().getConfiguration().orientation;
    

    /**
     * Popup window itself is as big as the window of the Activity. 
     * The keyboard can then be calculated by extracting the popup view bottom 
     * from the activity window height. 
     */
    private void handleOnGlobalLayout() 

        Point screenSize = new Point();
        activity.getWindowManager().getDefaultDisplay().getSize(screenSize);

        Rect rect = new Rect();
        popupView.getWindowVisibleDisplayFrame(rect);

        // REMIND, you may like to change this using the fullscreen size of the phone
        // and also using the status bar and navigation bar heights of the phone to calculate
        // the keyboard height. But this worked fine on a Nexus.
        int orientation = getScreenOrientation();
        int keyboardHeight = screenSize.y - rect.bottom;

        if (keyboardHeight == 0) 
            notifyKeyboardHeightChanged(0, orientation);
        
        else if (orientation == Configuration.ORIENTATION_PORTRAIT) 
            this.keyboardPortraitHeight = keyboardHeight; 
            notifyKeyboardHeightChanged(keyboardPortraitHeight, orientation);
         
        else 
            this.keyboardLandscapeHeight = keyboardHeight; 
            notifyKeyboardHeightChanged(keyboardLandscapeHeight, orientation);
        
    

    /**
     *
     */
    private void notifyKeyboardHeightChanged(int height, int orientation) 
        if (observer != null) 
            observer.onKeyboardHeightChanged(height, orientation);
        
    

    public interface KeyboardHeightObserver 
        void onKeyboardHeightChanged(int height, int orientation);
    

popupwindow.xml

<?xml version="1.0" encoding="utf-8"?>
<View
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/popuplayout"
    android:layout_
    android:layout_
    android:background="@android:color/transparent"
    android:orientation="horizontal"/>

MainActivity中使用

import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import kotlinx.android.synthetic.main.activity_main.*

/**
 * Created by nileshdeokar on 22/02/2018.
 */
class MainActivity : AppCompatActivity() , KeyboardHeightProvider.KeyboardHeightObserver  

    private lateinit var keyboardHeightProvider : KeyboardHeightProvider


    override fun onCreate(savedInstanceState: Bundle?) 
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        keyboardHeightProvider = KeyboardHeightProvider(this)
        parentActivityView.post  keyboardHeightProvider?.start() 
    

    override fun onKeyboardHeightChanged(height: Int, orientation: Int) 
        // In case of 18:9 - e.g. Samsung S8
        // here you get the height of the navigation bar as negative value when keyboard is closed.
        // and some positive integer when keyboard is opened.
    

    public override fun onPause() 
        super.onPause()
        keyboardHeightProvider?.setKeyboardHeightObserver(null)
    

    public override fun onResume() 
        super.onResume()
        keyboardHeightProvider?.setKeyboardHeightObserver(this)
    

    public override fun onDestroy() 
        super.onDestroy()
        keyboardHeightProvider?.close()
    

如需进一步帮助,您可以查看此here 的高级用法。

【讨论】:

【参考方案19】:

简单的单线解决方案

如上述许多答案中所建议的,例如

https://***.com/a/29938139/9640177 https://***.com/a/26118045/9640177 https://***.com/a/50775459/9640177 https://***.com/a/41057024/9640177

仅获取导航栏高度可能还不够。我们需要考虑 1. 导航栏是否存在, 2. 是在底部,还是在右侧或左侧, 3. 应用是否以多窗口模式打开。

幸运的是,您只需在根布局中设置 android:fitsSystemWindows="true" 即可轻松绕过所有冗长的编码。 Android 系统会自动为根布局添加必要的填充,以确保子视图不会进入导航栏或状态栏区域。

有一个简单的单行解决方案

android:fitsSystemWindows="true"

或以编程方式

findViewById(R.id.your_root_view).setFitsSystemWindows(true);

您也可以通过

获得根视图
findViewById(android.R.id.content).getRootView();
or
getWindow().getDecorView().findViewById(android.R.id.content)

有关获取根视图的更多详细信息,请参阅 - https://***.com/a/4488149/9640177

【讨论】:

这并不适合您希望将布局放在导航栏后面并在上方添加实际内容的安全边框的所有用例,例如地图活动上的 fab 按钮【参考方案20】:

我的处理剪裁+导航栏的版本

fun View.getCutoutRect(): Rect 
    return when 
        isInEditMode -> 
            val cutout = context.dpToPx(16f).roundToInt()
            Rect(cutout, cutout, cutout, cutout)
        
        Build.VERSION.SDK_INT >= Build.VERSION_CODES.M -> 
            val windowInsets = (context as? AppCompatActivity)?.window?.decorView?.rootWindowInsets ?: run 
                requestLayout()
                return Rect()
            
            val cutout = WindowInsetsCompat.toWindowInsetsCompat(windowInsets).displayCutout
            val systemBars = WindowInsetsCompat.toWindowInsetsCompat(windowInsets).getInsets(WindowInsetsCompat.Type.systemBars())

            Rect(
                maxOf(cutout?.safeInsetLeft ?: 0, systemBars.left),
                maxOf(cutout?.safeInsetTop ?: 0, systemBars.top),
                maxOf(cutout?.safeInsetRight ?: 0, systemBars.right),
                maxOf(cutout?.safeInsetBottom ?: 0, systemBars.bottom),
            )
        
        else -> 
            val savedRect = (this.getTag(R.id.view_insets_tag_id) as? Rect) ?: Rect()
            ViewCompat.setOnApplyWindowInsetsListener(this)  v, insets ->
                val cutout = insets.displayCutout
                val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
                val rect = Rect(
                    maxOf(cutout?.safeInsetLeft ?: 0, systemBars.left),
                    maxOf(cutout?.safeInsetTop ?: 0, systemBars.top),
                    maxOf(cutout?.safeInsetRight ?: 0, systemBars.right),
                    maxOf(cutout?.safeInsetBottom ?: 0, systemBars.bottom),
                )
                this.setTag(R.id.view_insets_tag_id, rect)
                if (savedRect != rect) 
                    requestLayout()
                
                return@setOnApplyWindowInsetsListener insets
            
            this.requestApplyInsets()
            savedRect
        
    

【讨论】:

以上是关于如何以编程方式获取 Android 导航栏的高度和宽度?的主要内容,如果未能解决你的问题,请参考以下文章

以编程方式快速更改导航栏的高度

如何获取Android手机底部导航栏的高度

如何以编程方式获取 iOS 状态栏高度

以编程方式更改标签栏和导航栏的颜色

如何以编程方式在 Android 中获取图像按钮的宽度和高度

如何以编程方式设置导航栏的标题?