24

Is there a way to know where this Navigation Bar will be displayed for landscape :

  • on Nexus 7 it is at the bottom
  • on a Nexus 5 it is on the right

enter image description here

halfer
  • 19,824
  • 17
  • 99
  • 186
ebtokyo
  • 2,369
  • 4
  • 23
  • 33
  • 2
    Why would you need to know that? – Robert Harvey Jan 11 '14 at 00:43
  • The [menu button is dead](http://android-developers.blogspot.com/2012/01/say-goodbye-to-menu-button.html). – ianhanniballake Jan 11 '14 at 00:44
  • Neither the Nexus 7 nor the Nexus 5 have a permanent MENU key. – CommonsWare Jan 11 '14 at 00:45
  • So I can avoid placing interactive UI near the back button for example. Also I would like to know the width/height available for landscape and portrait, this ahead of any orientation change. – ebtokyo Jan 11 '14 at 00:46
  • @ianhanniballake ... my bad, I was asking for the Navigation Bar, not the menu key. I edited the post. – ebtokyo Jan 11 '14 at 00:49
  • @RobertHarvey This info is vital when using a theme with a translucent navigation bar for those cases where `fitsSystemWindows=true` fails because it only adds a padding when you actually need a margin. – 0101100101 Feb 22 '18 at 00:38

7 Answers7

8

Based in part on Pauland's answer (in turn based on the implementation of PhoneWindowManager), here is what I am using at the moment:

  public static boolean isSystemBarOnBottom(Context ctxt) {
    Resources res=ctxt.getResources();
    Configuration cfg=res.getConfiguration();
    DisplayMetrics dm=res.getDisplayMetrics();
    boolean canMove=(dm.widthPixels != dm.heightPixels &&
        cfg.smallestScreenWidthDp < 600);

    return(!canMove || dm.widthPixels < dm.heightPixels);
  }

This works on a Nexus 7 2012 and a Nexus 4, each running Android 5.1.

On devices that have a permanent MENU key, there is no system bar. Depending upon your use case, you may need to check for this case:

ViewConfiguration.get(ctxt).hasPermanentMenuKey()

(where ctxt is some Context)

Personally, I am using this to try to have a sliding panel be on the opposite axis from the system bar, as bezel swipes on the side with the system bar are a bit difficult to trigger. I would not use this, or any other algorithm (like the ones that depend upon getDecorView()), for anything critical.

CommonsWare
  • 986,068
  • 189
  • 2,389
  • 2,491
  • Hi CommonsWare. Will this solution work for the Navigation Bar as well? Also, can you provide some context on why canMove is needed? – sudocoder Jun 22 '15 at 15:42
  • @sudocoder: "Will this solution work for the Navigation Bar as well?" -- I am not sure what you are considering the "navigation bar" to be. That term has not been used since Honeycomb, IIRC. "why canMove is needed?" -- because on some devices and OS versions, the system bar changes position. – CommonsWare Jun 22 '15 at 15:47
  • by "Navigation Bar"I mean what the red arrows point to in the pics provided by OP. I will take some time to disect "(!canMove || dm.widthPixels < dm.heightPixels);" because it is not immediately intuitive why this works. Thanks for the help Mr. Murphy :) – sudocoder Jun 22 '15 at 15:59
  • 1
    @sudocoder: "I mean what the red arrows point to in the pics provided by OP" -- that's the system bar. "it is not immediately intuitive why this works" -- the only reason why "it works" is because that is what `PhoneWindowManager` uses to decide where to put the system bar. If `PhoneWindowManager` changes, so will `isSystemBarOnBottom()` have to change. This is all a hack, but a hack is all we got. – CommonsWare Jun 22 '15 at 16:01
  • using DisplayMetrics did not consistently work for me. When I start my Activity, DisplayMetrics can show width/height of the wrong orientation, (phone is in landscape, but shows portrait's width and height) thus incorrectly determining if the SystemBar is on the bottom. Instead, I used http://stackoverflow.com/a/1016941/1895452 – sudocoder Jun 22 '15 at 19:25
  • 1
    @CommonsWare, I'm not quite argee about the terminology. As `PhoneWindowManager` declares, those "back, home, recent apps" buttons are the Navigation Bar: `WindowState mStatusBar = null; WindowState mNavigationBar = null;` Moreover, even the layout file of navigation bar view is called [navigation_bar.xml](https://android.googlesource.com/platform/frameworks/base/+/64ab8fdcb0a3015d3e6c4db0a1d66443085fd673/packages/SystemUI/res/layout-sw600dp/navigation_bar.xml) – Dmitry Gryazin Mar 02 '16 at 13:04
6

By using the properties of the decor view in combination with the current DisplayMetrics you can find out on which side the navigation bar is positioned.

// retrieve the position of the DecorView
Rect visibleFrame = new Rect();
getWindow().getDecorView().getWindowVisibleDisplayFrame(visibleFrame);

DisplayMetrics dm = getResources().getDisplayMetrics();
// check if the DecorView takes the whole screen vertically or horizontally
boolean isRightOfContent = dm.heightPixels == visibleFrame.bottom;
boolean isBelowContent   = dm.widthPixels  == visibleFrame.right;
jankovd
  • 1,681
  • 1
  • 16
  • 22
6

My solution

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

public static int getNavigationBarHeight (Resources resources)
{
    if (!Utils.hasNavBar(resources))
        return 0;

    int orientation = resources.getConfiguration().orientation;

    //Only phone between 0-599 has navigationbar can move
    boolean isSmartphone = resources.getConfiguration().smallestScreenWidthDp < 600;
    if (isSmartphone && Configuration.ORIENTATION_LANDSCAPE == orientation)
        return 0;

    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 static int getNavigationBarWidth (Resources resources)
{
    if (!Utils.hasNavBar(resources))
        return 0;

    int orientation = resources.getConfiguration().orientation;

    //Only phone between 0-599 has navigationbar can move
    boolean isSmartphone = resources.getConfiguration().smallestScreenWidthDp < 600;

    if (orientation == Configuration.ORIENTATION_LANDSCAPE && isSmartphone)
    {
        int id = resources.getIdentifier("navigation_bar_width", "dimen", "android");
        if (id > 0)
            return resources.getDimensionPixelSize(id);
    }

    return 0;
}

Solution based on https://android.googlesource.com/platform/frameworks/base/+/9f65c4c34abb07bdda54649ed510af26f16e9c1b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java

Pauland
  • 1,974
  • 16
  • 25
3

Working Solution for me is:

public static boolean hasNavBar(Context context) {
        WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        Point realPoint = new Point();
        Display display = wm.getDefaultDisplay();
        display.getRealSize(realPoint);
        DisplayMetrics metrics = new DisplayMetrics();
        wm.getDefaultDisplay().getMetrics(metrics);
        return metrics.heightPixels + metrics.widthPixels != realPoint.y + realPoint.x;
    }

    public static boolean isSystemBarOnBottom(Context context) {
        WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        Point realPoint = new Point();
        Display display = wm.getDefaultDisplay();
        display.getRealSize(realPoint);
        DisplayMetrics metrics = new DisplayMetrics();
        wm.getDefaultDisplay().getMetrics(metrics);
        Configuration cfg = context.getResources().getConfiguration();
        boolean canMove = (metrics.widthPixels != metrics.heightPixels &&
                cfg.smallestScreenWidthDp < 600);

        return (!canMove || metrics.widthPixels < metrics.heightPixels);
    }
Fabian
  • 960
  • 13
  • 26
1

This is working with my app, I'm using this with translucent statusbar and navbar to set the padding.

boolean navBarOnTheBottom(){
        DisplayMetrics displaymetrics = new DisplayMetrics();
        getWindowManager().getDefaultDisplay().getMetrics(displaymetrics);
        int viewHeight = displaymetrics.heightPixels;
        if (bkg.getHeight() == viewHeight)
        {
            Log.d(TAG, "nav bar on the side");
            return false;
        }
        else{
            Log.d(TAG, "nav bar on the bottom");
            return true;
        }
    }

bkg it's the main linearLayout that holds all my app views. Make sure that bkg.getHeight() doesn't give you 0, with some layouts it did give me 0

EDIT: Get the layout height like this if the above one gives you 0

@Override
    public void onWindowFocusChanged (boolean hasFocus) {
        // the height will be set at this point
        bkgHeight = bkg.getHeight();
    }
DoubleP90
  • 1,249
  • 1
  • 16
  • 29
1

Be aware that starting with Android 7.1 the navigation bar may also be located on the LEFT side of the screen. To make sure you always know the position of the navigation bar, use a DisplayListener to check for 180° screen changes on Android 7.1 and above like here: How to detect screen rotation through 180 degrees from landscape to landscape orientation?

I've been successfully and reliably using this code to determine any changes to the position of the navigation bar:

DisplayManager.DisplayListener displayListener;

@Override
protected void onCreate(Bundle savedInstanceState) {

    ...

    displayListener = new DisplayManager.DisplayListener() {
        private int lastRotation =
                ((WindowManager) getSystemService(Context.WINDOW_SERVICE))
                                           .getDefaultDisplay().getRotation();

        @Override
        public void onDisplayAdded(int displayId) {
        }

        @Override
        public void onDisplayChanged(int displayId) {
            int rotation = ((WindowManager) getSystemService(Context.WINDOW_SERVICE))
                                   .getDefaultDisplay().getRotation();
            if (rotation == Surface.ROTATION_90
                        && lastRotation == Surface.ROTATION_270
                    || rotation == Surface.ROTATION_270
                        && lastRotation == Surface.ROTATION_90) {
                onNavigationBarMoved(true);
            }
            lastRotation = rotation;
        }

        @Override
        public void onDisplayRemoved(int displayId) {
        }
    };
    DisplayManager displayManager =
            (DisplayManager) getSystemService(Context.DISPLAY_SERVICE);
    displayManager.registerDisplayListener(displayListener, null);
    onNavigationBarMoved(false);
}

@Override
public void onConfigurationChanged(Configuration newConfig) {
    super.onConfigurationChanged(newConfig);
    onNavigationBarMoved(false);
}

@Override
protected void onDestroy() {
    if (displayListener != null) {
        DisplayManager displayManager =
                (DisplayManager) getSystemService(Context.DISPLAY_SERVICE);
        displayManager.unregisterDisplayListener(displayListener);
    }
    super.onDestroy();
}

protected void onNavigationBarMoved(boolean landscapeScreenRotation) {
    boolean leftSideNavigationBar = Build.VERSION.SDK_INT > Build.VERSION_CODES.N
                         && ((WindowManager) getSystemService(Context.WINDOW_SERVICE))
                                .getDefaultDisplay().getRotation() == Surface.ROTATION_270;
    // add whatever else you need here
}

Bear in mind that an Activity won't be recreated when rotating a device already in landscape by 180°, so inside onNavigationBarMoved the value of landscapeScreenRotation will tell you when this happens so you can readjust your UI accordingly.

0101100101
  • 5,786
  • 6
  • 31
  • 55
  • On Huawei P30 pro , when Surface.ROTATION_90 and Surface.ROTATION_270 , navigation bar on the right. – woltran Jul 20 '19 at 11:35
1

All of the existing answers don't work with FLAG_LAYOUT_NO_LIMITS. I found a new solution:

@Override
    public void onResume() {
        super.onResume();

        ViewCompat.setOnApplyWindowInsetsListener(requireActivity().getWindow().getDecorView(), (v, insets) -> {    
            int sysbottom = insets.getInsets(WindowInsetsCompat.Type.systemBars()).bottom;
            int systop = insets.getInsets(WindowInsetsCompat.Type.systemBars()).top;
            int sysleft = insets.getInsets(WindowInsetsCompat.Type.systemBars()).left;
            int sysright = insets.getInsets(WindowInsetsCompat.Type.systemBars()).right;

            return insets;
        });
    }

Here are sysbottom, systop, sysleft, sysright - values of system bars indens for navigation bar and status bar in pixel.

halfer
  • 19,824
  • 17
  • 99
  • 186
katemed
  • 21
  • 2