Search code examples
androiduser-interfaceandroid-4.4-kitkat

Get position and visibility of translucent Android navigation bar


I'm working on a cross-platform app which has been ported to Android. Due to its non-Android heritage it doesn't make extensive use of Android UI elements – the main app window is one single View.

I would like to implement translucent system (status and navigation) bars, as supported from KitKat onwards, while ensuring that UI elements inside the main View are not obstructed by them.

This answer shows how to get the default height for these bars (there is also navigation_bar_height_landscape for navigation bar height in landscape mode, and navigation_bar_width for the width of a vertical navigation bar). But these are only the system's default sizes and contain no information if the navigation bar is currently being shown, and whether it is showing at the right or on the bottom edge of the screen.

The other answers I have found so far probably work fine for non-translucent system bars: they simply compare total screen size to the size of the main app view, and the difference is the space occupied by the system bars. However, with translucent system bars, the size difference is zero and this approach won't work.

How can I reliably determine if the navigation bar is being displayed, and in which position?


Solution

  • Looking around a bit, including this part of the AOSP code, I came up with the following:

            // determine if navigation bar is going to be shown
            boolean isNavShowing;
            if (Build.VERSION.SDK_INT >= 13) {
                isNavShowing = ViewConfiguration.get(activity.getApplication()).hasPermanentMenuKey();
            }
    
            // determine where the navigation bar would be displayed
            boolean isNavAtBottom;
            if (Build.VERSION.SDK_INT >= 13) {
                isNavAtBottom = (activity.getResources().getConfiguration().orientation != Configuration.ORIENTATION_LANDSCAPE)
                        || (activity.getResources().getConfiguration().smallestScreenWidthDp >= 600);
            }
    

    You can then calculate the following insets:

    • Left: 0
    • Top: value of status_bar_height
    • Right: 0 if navigation bar is not at bottom, else navigation_bar_width
    • Bottom: 0 if navigation bar is not at bottom, else: if orientation is landscape (see code above), navigation_bar_height_landscape; else navigation_bar_height.

    Defaults, as per dimens.xml, are:

    • status_bar_height: 24 dp
    • navigation_bar_height: 48 dp
    • navigation_bar_height_landscape: 48 dp
    • navigation_bar_width: 48 dp

    (you might want to fall back to these if somehow you fail to get the actual values.)

    The logic for isNavShowing is a variation on something I implemented a while ago. It reliably detects the presence of a physical Menu button even on a OnePlusOne running CyanogenMod (where the user can switch between a navigation bar and hardware buttons via Settings). Even when adding a software Menu button to the navigation bar (which seems to be a CM addition), the navigation bar is still detected correctly.

    The logic for isNavAtBottom is mostly taken from the findNavigationBar() function in the source file I linked above and should work unless you are using a heavily customized build of Android.