ebtokyo
ebtokyo

Reputation: 2379

Detect Android Navigation Bar orientation

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

enter image description here

Upvotes: 24

Views: 11849

Answers (7)

katemed
katemed

Reputation: 31

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.

Upvotes: 2

0101100101
0101100101

Reputation: 5911

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.

Upvotes: 1

Fabian
Fabian

Reputation: 978

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

Upvotes: 3

CommonsWare
CommonsWare

Reputation: 1007359

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.

Upvotes: 8

Pauland
Pauland

Reputation: 2014

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

Upvotes: 6

DoubleP90
DoubleP90

Reputation: 1249

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

Upvotes: 1

jankovd
jankovd

Reputation: 1701

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;

Upvotes: 6

Related Questions