134

The black navigation bar on the bottom of the screen is not easily removable in Android. It has been part of Android since 3.0 as a replacement for hardware buttons. Here is a picture:

System Bar

How can I get the size of the width and the height of this UI element in pixels?

Daniel
  • 2,339
  • 9
  • 21
  • 28
Kevik
  • 8,421
  • 17
  • 88
  • 141
  • The best solution for this problem is added here. We can identify, Is navigation bar present in device by comparing display metrics and real metrics. Look my answer, I added full code to find out actual navigation bar size for whole android devices. – Sino Raj Sep 30 '16 at 09:59

19 Answers19

190

Try below code:

Resources resources = context.getResources();
int resourceId = resources.getIdentifier("navigation_bar_height", "dimen", "android");
if (resourceId > 0) {
    return resources.getDimensionPixelSize(resourceId);
}
return 0;
Sanket
  • 2,914
  • 1
  • 14
  • 19
  • 9
    Thanks. +1. Do you know how stable this approach is? Are the resource identifiers going to be subject to change across different platform versions? – Ben Pearson Oct 30 '14 at 19:10
  • 67
    Note that this piece of code does not return 0 on devices without navigation bar. Tested on Samsung S2 and S3. I got 72 and 96. – Egis Apr 13 '15 at 14:38
  • 6
    @Egidijus Look at my answer, it will return 0 for devices that have physical navigation http://stackoverflow.com/a/29938139/1683141 – Mdlc May 01 '15 at 18:13
  • 1
    This does not work for me. I still get a value returned for devices which do not have the nav bar present on the screen. My testing shows thev value returned in this code is = Nexus5:144 | Nexus7:75 | SamsungS10:96 | SamsungNoteII:96. Samsung do not have a navbar present so using this code will make you think it does have. – se22as Jun 10 '15 at 11:28
  • 1
    galaxy s6 edge also does not return 0, although there's no navigation bar on the bottom. returns 192. – agamov Aug 11 '15 at 13:45
  • 1
    Many Sony Xperia return 0 when actually they do have navigation bar. Not a very reliable method. – AxeEffect Oct 31 '15 at 17:29
  • it's because `48dp`.... from https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/core/res/res/values/dimens.xml – Bugs Happen Dec 29 '15 at 06:06
  • 5
    This code shows the **default** size of the navigation bar (try also `navigation_bar_height_landscape` for navigation bar height in landscape mode, and `navigation_bar_width` for the width of a vertical navigation bar). You have to find out separately if and where the navigation bar is actually showing, e.g. by testing for the presence of a physical menu button. Maybe you can find some other way in the Android souce code at https://android.googlesource.com/platform/frameworks/base/+/android-4.2.2_r1/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java – user149408 Jan 18 '16 at 21:19
  • The best solution for this problem is added here. We can identify, Is navigation bar present in device by comparing display metrics and real metrics. Look my answer, I added full code to find out actual navigation bar size for whole android devices. – Sino Raj Sep 30 '16 at 09:58
  • can some one please let me where to place this piece of code to get dimensions. – sahithi Oct 14 '17 at 06:56
  • @Egis does my solution work for you https://stackoverflow.com/a/48070797/477790 ? – Bakhshi Jan 04 '18 at 00:15
  • 1
    The modern answer to this issue is [John's answer below](https://stackoverflow.com/a/50775459/901432) – Lance Johnson Dec 12 '18 at 04:37
  • this will cause Samsung A3 devices to hang without exception, so that the OS kills your application for some weird reason (updated to android 8) – Kibotu Nov 28 '20 at 12:01
104

I get navigation bar size by comparing app-usable screen size with real screen size. I assume that navigation bar is present when app-usable screen size is smaller than real screen size. Then I calculate navigation bar size. This method works with API 14 and up.

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;
}

UPDATE

For a solution that takes into account display cutouts please check John's answer.

Egis
  • 4,048
  • 4
  • 34
  • 50
  • 2
    This is the only solution which works for me across all diff of devices I have been testing. My req is to determine if the navBar is at the bottom of the screen taking into account orientation. I have slightly tweaked the code in the answer to just return me the size of the nav bar at the bottom of the screen, 0 indicates its not present. My Testing shows that Nexus5 = portrait:144, Landscape:0 | Nexus7 = portrait:96, Landscape:96 | Samsung Note II = portrait:0, Landscape:0 | Samsung S10 = portrait:0, Landscape:0 – se22as Jun 10 '15 at 11:25
  • This solution works just perfect! Even I set transparent tint color to navigation bar. Thanks! I will test on more devices. – MewX Jun 17 '15 at 17:31
  • Note in my testing, i was testing using an emulator for Nexus 5, read devices for Nexus 7, Samsung Note II and Samsung S10 – se22as Jul 09 '15 at 13:34
  • 2
    I can also confirm Danylo's solution works on some models only. This solution by Egidijus is a better working solution, and has worked for all models tested so far. Thank you for this. – Bisclavret Aug 06 '15 at 10:07
  • What about the status bar? How come it doesn't need to be considered here? – android developer Sep 02 '15 at 14:46
  • 2
    Status bar is part of app usable screen. – Egis Sep 04 '15 at 08:21
  • @Egidijus Can `getRealScreenSize()` be shortened to `activity.getWindow().getDecorView().getHeight();`? – Sufian Mar 03 '16 at 12:27
  • @Sufian I don't think so but you can test it. It may work on some APIs. – Egis Mar 03 '16 at 12:42
  • The best solution for this problem is added here. We can identify, Is navigation bar present in device by comparing display metrics and real metrics. Look my answer, I added full code to find out actual navigation bar size for whole android devices. – Sino Raj Sep 30 '16 at 10:00
  • Why do you return Point instead of int (pixels)? – localhost Nov 15 '16 at 11:22
  • No particular reason. You can return int[], Pair or whatever you want. I chose Point. – Egis Nov 15 '16 at 11:44
  • Nailed it, it works like magic . . :) did some changes to make available on React Native :) Thanks a lot – Website Is Fun Aug 30 '17 at 05:03
  • 1
    This works, but doesn't consider if the existing bar is hidden or not. Any idea how to check this? – X-HuMan Oct 11 '17 at 13:57
  • Please be careful that this approach will break in [Multi-Window Mode](https://developer.android.com/guide/topics/ui/multi-window.html). Remember to set `android:resizeableActivity="false"` in your Manifest. – Sira Lam Mar 16 '18 at 09:27
  • And I further found out that the layout can break sometimes if you dismiss the keyboard programmatically. – Sira Lam May 25 '18 at 07:56
  • Thanks for the class. I ported to Xamarin.Android. [Take if you want.](https://gist.github.com/Nyconing/ddf1e6bae7c59c3c46fb1fcec50fb878) – nyconing Dec 13 '18 at 04:36
  • 1
    It is not working for Android P while Navigation Gesture is on instead of navigation bar. Can you please guide me how to do resolved for this situation – Nik Mar 04 '19 at 13:38
  • 2
    This also doesn't take into account DisplayCutout – Gauthier Mar 22 '19 at 14:24
  • It doesn't quite work as expected on Chromebooks where the app can be in a window, which can be positioned anywhere on the screen. I'd guess it's not an issue for most apps. – TTransmit May 28 '19 at 13:02
  • Setting something appropriate for the app to do in `if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N && activity.isInMultiWindowMode)` worked to handle the edge case on Chromebooks for me. – TTransmit May 28 '19 at 14:34
  • This solution is good but it is incorrect on some devices. I will add my solution at below. – Anh Duy Dec 12 '19 at 09:44
42

The NavigationBar height varies for some devices, but as well for some orientations. First you have to check if the device has a navbar, then if the device is a tablet or a not-tablet (phone) and finally you have to look at the orientation of the device in order to get the correct height.

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;
}
Leo Droidcoder
  • 11,898
  • 3
  • 55
  • 48
Mdlc
  • 6,388
  • 11
  • 48
  • 95
  • This does not work for me, Nexus5 returns true for hasMenuKey and hasBackKey therefore it thinks there is no navBar. (Nexus7 correctly returns false for hasMenuKey and hasBackKey) – se22as Jun 10 '15 at 11:26
  • 5
    I've tested this code on the Nexus 5, and it returns false for both. Are you sure you're not on an emulator or rom? – Mdlc Jun 10 '15 at 15:43
  • Yes i was using an emulator, sorry i should have clarified in my statement previously – se22as Jul 09 '15 at 13:33
  • shouldn't this be !hasMenuKey || !hasBackKey instead of !hasMenuKey && !hasBackKey ? please verify – yongsunCN Mar 06 '16 at 03:52
  • @yongsunCN In my experiencing, there don't exist any devices with some buttons as physical buttons and some buttons as software keys (considering only the menu and back keys). Therefore, both approaches will fulfill. – Mdlc Apr 06 '16 at 22:17
  • Unfortunately, this doesn't work correctly on Sony Xperia Z3 Compact (and possibly others - but I don't have them). – lenooh Sep 04 '16 at 22:10
  • The best solution for this problem is added here. We can identify, Is navigation bar present in device by comparing display metrics and real metrics. Look my answer, I added full code to find out actual navigation bar size for whole android devices. – Sino Raj Sep 30 '16 at 10:00
  • 2
    Unfortunately, doesn't work on Sony Xperia Z3 `hasBackKey = false!` although it should be true. The following worked instead: `boolean navBarExists = getResources().getBoolean(getResources().getIdentifier("config_showNavigationBar", "bool", "android"));` – Hamzeh Soboh Mar 13 '17 at 09:59
  • Doesn't work on Pixel XL, hasBackKey is true but should be false. – Emil Adz Sep 11 '17 at 09:26
  • Not working on Sony Xperia Z3, `hasBackKey` is true but should be false – mbo Oct 20 '17 at 08:07
  • @MariusBoepple What if you include the fix of @HamzehSoboh? – Mdlc Oct 20 '17 at 14:54
27

Actually the navigation bar on tablets (at least Nexus 7) has different size in portrait and landscape so this function should look like this:

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;
}
Danylo Volokh
  • 3,613
  • 2
  • 28
  • 38
  • 6
    To retrieve orientation from context, use `context.getResources().getConfiguration().orientation` – Hugo Gresse Feb 12 '15 at 14:15
  • The best solution for this problem is added here. We can identify, Is navigation bar present in device by comparing display metrics and real metrics. Look my answer, I added full code to find out actual navigation bar size for whole android devices. – Sino Raj Sep 30 '16 at 10:03
26

I think better answer is here because it allows you to get even cutout height too.

Take your root view, and add setOnApplyWindowInsetsListener (or you can override onApplyWindowInsets from it), and take insets.getSystemWindowInsets from it.

In my camera activity, i add padding equal to the systemWindowInsetBottom to my bottom layout. And finally, it fix cutout issue.

Camera activity insets

with appcompat it is like this

ViewCompat.setOnApplyWindowInsetsListener(mCameraSourcePreview, (v, insets) -> {
    takePictureLayout.setPadding(0,0,0,insets.getSystemWindowInsetBottom());
    return insets.consumeSystemWindowInsets();
});

without appcompat, this:

mCameraSourcePreview.setOnApplyWindowInsetsListener((v, insets) -> { ... })
John
  • 1,153
  • 13
  • 10
13

I hope this helps you

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;
}
5

This is my code to add paddingRight and paddingBottom to a View to dodge the Navigation Bar. I combined some of the answers here and made a special clause for landscape orientation together with isInMultiWindowMode. The key is to read navigation_bar_height, but also check config_showNavigationBar to make sure we should actually use the height.

None of the previous solutions worked for me. As of Android 7.0 you have to take Multi Window Mode into consideration. This breaks the implementations comparing display.realSize with display.size since realSize gives you the dimensions of the whole screen (both split windows) and size only gives you the dimensions of your App window. Setting padding to this difference will leave your whole view being padding.

/** 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);
}
marcus
  • 86
  • 1
  • 2
2

The solution proposed by Egidijus and works perfectly for Build.VERSION.SDK_INT >= 17

But I got "NoSuchMethodException" during execution of the following statement with Build.VERSION.SDK_INT < 17 on my device:

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

I have modified the method getRealScreenSize() for such cases:

else if(Build.VERSION.SDK_INT >= 14) 
{
    View decorView = getActivity().getWindow().getDecorView();
    size.x = decorView.getWidth();
    size.y = decorView.getHeight();
}
Chebyr
  • 2,133
  • 1
  • 16
  • 29
2

How to get the height of the navigation bar and status bar. This code works for me on some Huawei devices and Samsung devices. Egis's solution above is good, however, it is still incorrect on some devices. So, I improved it.

This is code to get the height of status bar

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
    }

This method always returns the height of navigation bar even when the navigation bar is hidden.

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

NOTE: on Samsung A70, this method returns the height of the status bar + height of the navigation bar. On other devices (Huawei), it only returns the height of the Navigation bar and returns 0 when the navigation bar is hidden.

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
        }
    }

This is code to get height of navigation bar and status bar

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
                }
Anh Duy
  • 1,004
  • 14
  • 21
2

I've done this, it works on every device I tested, and even on emulators:

// 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());
}
Matteo
  • 71
  • 1
  • 5
2

New answer in 2021 comes to the rescue


insipred from Egis's answer:

context.navigationBarHeight

where the extension getter is

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
    }
}

tested on:

  • emulators with navigation bars
    • pixel 3a (api 30)
    • pixel 2 (api 28)
    • pixel 3 (api 25)
    • pixel 2 (api 21)
  • Xiaomi Poco f2 pro with & without navigation bar(full display)
Haytham Anmar
  • 196
  • 3
  • 7
1

The height of the bottom Navigation bar is 48dp (in both portrait and landscape mode) and is 42dp when the bar is placed vertically.

Aritra Roy
  • 14,384
  • 6
  • 63
  • 99
1

I resolved this issue for all devices(including Nexus 5, Samsung Galaxy Nexus 6 edge+, Samsung S10, Samsung Note II etc.). I think this will help you to handle device dependant issues.

Here I am adding two types of codes,

Java Code(for 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;
    }
}

And C# code(for 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;
        }
Sino Raj
  • 6,121
  • 2
  • 19
  • 23
  • `Display.getRealMetrics()` requires API level 17 – Weizhi Oct 16 '16 at 10:13
  • if (Build.VERSION.SdkInt >= BuildVersionCodes.JellyBeanMr2) already checked. – Sino Raj Oct 16 '16 at 20:14
  • The code need to fix. My LG Nexus 5X will get 0.0 due to: [1] When `screenOrientation` is default, `hasPermanentMenuKey` is `false` only if not rotate. [2] When `screenOrientation` is landscape, it fall into `displayMetrics.heightPixels != realDisplayMetrics.heightPixels)`'s else. [3] When `screenOrientation` is portrait, `hasPermanentMenuKey` is `false`. – Fruit Dec 28 '16 at 18:56
  • it works, but on 4.x if(hasPermanentMenuKey) is waste, comment it – djdance Jun 08 '17 at 17:39
  • Your condition should be if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1). API 17 is required in order to do public void getRealMetrics (DisplayMetrics outMetrics). See docs: https://developer.android.com/reference/kotlin/android/util/DisplayMetrics – portfoliobuilder Jun 20 '18 at 00:05
1

Combining the answer from @egis and others - this works well on a variety of devices, tested on Pixel EMU, Samsung S6, Sony Z3, Nexus 4. This code uses the display dimensions to test for availability of nav bar and then uses the actual system nav bar size if present.

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

}
Meanman
  • 1,363
  • 18
  • 16
  • If I want to programmatically set the height of a TextView to the bottom of the usableheight of the screen, how would I use this class? – Naveed Abbas Jul 19 '18 at 08:38
1

Tested code for getting height of navigation bar (in pixels):

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;
}

Tested code for getting height of status bar (in pixels):

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;
}

Converting pixels to dp:

public static int pxToDp(int px) {
    return (int) (px / Resources.getSystem().getDisplayMetrics().density);
}
Sagar
  • 2,480
  • 2
  • 24
  • 34
0

Here is how I solved this. I made a hideable bottom bar which needed padding depending on if there was a navigation bar or not (capacitive, on-screen or just pre lollipop).


View

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;
}
Jonas Borggren
  • 2,413
  • 1
  • 19
  • 39
0

In my case where I wanted to have something like this:

Screenshot

I had to follow the same thing as suggested by @Mdlc but probably slightly simpler (targeting only >= 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)

It works on landscape too:

landscape

Edit The above solution does not work correctly in multi-window mode where the usable rectangle is not smaller just due to the navigation bar but also because of custom window size. One thing that I noticed is that in multi-window the navigation bar is not hovering over the app so even with no changes to DecorView padding we have the correct behaviour:

Multi-window with no changes to decor view padding Single-window with no changes to decor view padding

Note the difference between how navigation bar is hovering over the bottom of the app in these to scenarios. Fortunately, this is easy to fix. We can check if app is multi window. The code below also includes the part to calculate and adjust the position of toolbar (full solution: 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
    }

Result on Samsung popup mode:

enter image description here

Bakhshi
  • 1,239
  • 12
  • 25
0

In case of Samsung S8 none of the above provided methods were giving proper height of navigation bar so I used the KeyboardHeightProvider keyboard height provider android. And it gave me height in negative values and for my layout positioning I adjusted that value in calculations.

Here is 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_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/transparent"
    android:orientation="horizontal"/>

Usage in 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()
    }
}

For any further help you can have a look at advanced usage of this here.

Nilesh Deokar
  • 2,777
  • 27
  • 47
0

Simple One-line Solution

As suggested in many of above answers, for example

Simply getting navigation bar height may not be enough. We need to consider whether 1. navigation bar exists, 2. is it on the bottom, or right or left, 3. is app open in multi-window mode.

Fortunately you can easily bypass all the long coding by simply setting android:fitsSystemWindows="true" in your root layout. Android system will automatically take care of adding necessary padding to the root layout to make sure that the child views don't get into the navigation bar or statusbar regions.

There is a simple one line solution

android:fitsSystemWindows="true"

or programatically

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

you may also get root view by

findViewById(android.R.id.content).getRootView();
or
getWindow().getDecorView().findViewById(android.R.id.content)

For more details on getting root-view refer - https://stackoverflow.com/a/4488149/9640177

mayank1513
  • 4,971
  • 3
  • 23
  • 70
  • this does not suit all use-cases where you want to have your layout behind the nav bar and add safe-border of your actual content above, e.g. fab button on a map activity – Kibotu Nov 28 '20 at 12:04