Jak programowo uzyskać wysokość i szerokość paska nawigacyjnego systemu Android?


122

Czarny pasek nawigacji u dołu ekranu nie jest łatwy do usunięcia w systemie Android. Jest częścią Androida od 3.0 jako zamiennik przycisków sprzętowych. Oto zdjęcie:

Pasek systemu

Jak mogę uzyskać rozmiar szerokości i wysokości tego elementu interfejsu użytkownika w pikselach?


Najlepsze rozwiązanie tego problemu zostało dodane tutaj. Możemy zidentyfikować, czy pasek nawigacji jest obecny w urządzeniu, porównując wskaźniki wyświetlania z rzeczywistymi danymi. Spójrz na moją odpowiedź, dodałem pełny kod, aby sprawdzić rzeczywisty rozmiar paska nawigacyjnego dla całych urządzeń z Androidem.
Sino Raj

Odpowiedzi:


178

Wypróbuj poniższy kod:

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

7
Dzięki. +1. Czy wiesz, jak stabilne jest to podejście? Czy identyfikatory zasobów będą podlegać zmianom w różnych wersjach platformy?
Ben Pearson

59
Zwróć uwagę, że ten fragment kodu nie zwraca 0 na urządzeniach bez paska nawigacji. Przetestowano na Samsungach S2 i S3. Dostałem 72 i 96.
Egis

4
@Egidijus Spójrz na moją odpowiedź, zwróci 0 dla urządzeń, które mają fizyczną nawigację stackoverflow.com/a/29938139/1683141
Mdlc

1
galaxy s6 edge również nie zwraca 0, chociaż na dole nie ma paska nawigacji. zwraca 192.
agamov

5
Ten kod pokazuje domyślny rozmiar paska nawigacyjnego (spróbuj również navigation_bar_height_landscapedla wysokości paska nawigacji w trybie poziomym i navigation_bar_widthdla szerokości pionowego paska nawigacji). Musisz osobno dowiedzieć się, czy i gdzie faktycznie wyświetla się pasek nawigacji, np. Testując obecność fizycznego przycisku menu. Może znajdziesz inny sposób w kodzie źródłowym Androida na android.googlesource.com/platform/frameworks/base/+/…
user149408

103

Rozmiar paska nawigacji uzyskuje, porównując rozmiar ekranu, który można wykorzystać w aplikacji, z rzeczywistym rozmiarem ekranu. Zakładam, że pasek nawigacji jest obecny, gdy rozmiar ekranu, z którego można korzystać przez aplikację, jest mniejszy niż rzeczywisty rozmiar ekranu. Następnie obliczam rozmiar paska nawigacyjnego. Ta metoda działa z API 14 i nowszymi.

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

AKTUALIZACJA

Aby znaleźć rozwiązanie uwzględniające wycięcia w ekranie, sprawdź odpowiedź Johna .


2
To jedyne rozwiązanie, które działa u mnie na wszystkich urządzeniach, które testowałem. Moim zadaniem jest określenie, czy pasek nawigacyjny znajduje się na dole ekranu, biorąc pod uwagę orientację. Nieznacznie poprawiłem kod w odpowiedzi, aby po prostu zwrócił mi rozmiar paska nawigacyjnego u dołu ekranu, 0 oznacza, że ​​nie jest obecny. Moje testy pokazują, że Nexus5 = portret: 144, pozioma: 0 | Nexus7 = pionowo: 96, poziomo: 96 | Samsung Note II = pionowo: 0, poziomo: 0 | Samsung S10 = portret: 0,
poziomo

2
Mogę również potwierdzić, że rozwiązanie Danylo działa tylko w niektórych modelach. To rozwiązanie firmy Egidijus jest lepiej działającym rozwiązaniem i działa dla wszystkich testowanych do tej pory modeli. Dziękuję Ci za to.
Bisclavret

2
Pasek stanu jest częścią ekranu aplikacji.
Egis

1
To działa, ale nie bierze pod uwagę, czy istniejący pasek jest ukryty, czy nie. Masz pomysł, jak to sprawdzić?
X-HuMan,

1
Nie działa w systemie Android P, gdy zamiast paska nawigacji jest włączony gest nawigacji. Czy możesz mi pomóc, jak rozwiązać tę sytuację
Nik

36

Wysokość paska nawigacji jest różna dla niektórych urządzeń, ale także dla niektórych orientacji. Najpierw musisz sprawdzić, czy urządzenie ma pasek nawigacyjny, następnie czy urządzenie jest tabletem czy nie tabletem (telefonem) i na końcu musisz spojrzeć na orientację urządzenia, aby uzyskać odpowiednią wysokość.

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

To nie działa dla mnie, Nexus5 zwraca true dla hasMenuKey i hasBackKey, dlatego uważa, że ​​nie ma navBar. (Nexus7 poprawnie zwraca false dla hasMenuKey i hasBackKey)
se22as

5
Przetestowałem ten kod na Nexusie 5 i zwraca on fałsz dla obu. Czy na pewno nie korzystasz z emulatora ani romu?
Mdlc

Tak, korzystałem z emulatora, przepraszam, powinienem był wcześniej wyjaśnić w moim oświadczeniu
se22as

nie powinno tak być! hasMenuKey || ! hasBackKey zamiast! hasMenuKey &&! hasBackKey? proszę zweryfikować
yongsunCN

2
Niestety nie działa na Sony Xperii Z3, hasBackKey = false!chociaż powinno być prawdą. Zamiast tego zadziałało: boolean navBarExists = getResources().getBoolean(getResources().getIdentifier("config_showNavigationBar", "bool", "android"));
Hamzeh Soboh

27

Właściwie pasek nawigacji na tabletach (przynajmniej Nexus 7) ma inny rozmiar w orientacji pionowej i poziomej, więc ta funkcja powinna wyglądać następująco:

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

6
Aby wydobyć orientację z kontekstu, użyjcontext.getResources().getConfiguration().orientation
Hugo Gresse,

Najlepsze rozwiązanie tego problemu zostało dodane tutaj. Możemy zidentyfikować, czy pasek nawigacji jest obecny w urządzeniu, porównując wskaźniki wyświetlania z rzeczywistymi danymi. Spójrz na moją odpowiedź, dodałem pełny kod, aby sprawdzić rzeczywisty rozmiar paska nawigacyjnego dla całych urządzeń z Androidem.
Sino Raj

17

Myślę, że lepsza odpowiedź jest tutaj, ponieważ pozwala również uzyskać równą wysokość wycięcia.

Weź swój główny widok i dodaj setOnApplyWindowInsetsListener (lub możesz nadpisać z niego onApplyWindowInsets) i pobierz z niego insets.getSystemWindowInsets.

W mojej aktywności kamery dodaję dopełnienie równe systemWindowInsetBottom do mojego dolnego układu. I wreszcie rozwiązuje problem z wycięciem.

Wstawki aktywności kamery

z appcompat jest tak

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

bez appcompat, to:

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

Czy można setOnApplyWindowInsetsListenerzamiast tego użyć ? Jeśli tak to jak? i dlaczego rozróżniłeś LOLLIPOP od reszty?
programista Androida

tak, jest to możliwe i jest lepsze. naprawię odpowiedź. i różnicować nie trzeba też
John

1
To najlepsza odpowiedź. Dosłownie cię kocham.
Himanshu Rawat

1
To jest prawdziwa odpowiedź.
rozpoczęcie

1
Odbiornik nigdy nie jest wykonywany, gdy wywołuję to z onCreate (). Czy coś mi brakuje?
howettl

12

mam nadzieję, że to Ci pomoże

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

To jest mój kod, aby dodać paddingRight i paddingBottom do widoku, aby uniknąć paska nawigacji. Połączyłem tutaj niektóre odpowiedzi i stworzyłem specjalną klauzulę dotyczącą orientacji poziomej wraz z isInMultiWindowMode. Kluczem jest przeczytanie navigation_bar_height , ale także sprawdzenie config_showNavigationBar aby upewnić się, że faktycznie powinniśmy używać wysokości.

Żadne z poprzednich rozwiązań nie zadziałało. Od wersji Android 7.0 należy wziąć pod uwagę tryb wielu okien. To łamie implementacje porównujące display.realSize z display.size, ponieważ realSize podaje wymiary całego ekranu (oba podzielone okna), a rozmiar podaje tylko wymiary okna aplikacji. Ustawienie dopełnienia na tę różnicę spowoduje, że cały widok będzie wypełnieniem.

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

1

Wysokość dolnego paska nawigacji wynosi 48 dp (zarówno w trybie pionowym, jak i poziomym) i 42 dp, gdy pasek jest umieszczony pionowo.


4
Wysokość jest ZWYKLE 48 dp. Ale niektórzy producenci mogą to zmienić, niektóre niestandardowe ROMy mogą to zmienić i tak dalej. Lepiej polegać na odpowiedzi Danylo ( stackoverflow.com/a/26118045/1377145 ), aby uzyskać dokładny rozmiar
Hugo Gresse

To nawet lepiej. Dzięki.
Aritra Roy,

umieszczone pionowo? jak w trybie poziomym?
sudocoder

1

Rozwiązanie zaproponowane przez Egidijusa i działa doskonale dla Build.VERSION.SDK_INT> = 17

Ale dostałem „NoSuchMethodException” podczas wykonywania poniższej instrukcji z Build.VERSION.SDK_INT <17 na moim urządzeniu:

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

Zmodyfikowałem metodę getRealScreenSize () dla takich przypadków:

else if(Build.VERSION.SDK_INT >= 14) 
{
    View decorView = getActivity().getWindow().getDecorView();
    size.x = decorView.getWidth();
    size.y = decorView.getHeight();
}

1

Rozwiązałem ten problem na wszystkich urządzeniach (w tym Nexus 5, Samsung Galaxy Nexus 6 edge +, Samsung S10, Samsung Note II itp.). Myślę, że pomoże ci to rozwiązać problemy zależne od urządzenia.

Tutaj dodam dwa rodzaje kodów,

Kod Java (dla natywnego Androida):

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

I kod C # (dla 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()wymaga poziomu API 17
Weizhi

if (Build.VERSION.SdkInt> = BuildVersionCodes.JellyBeanMr2) już zaznaczone.
Sino Raj

Kod trzeba naprawić. Mój LG Nexus 5X otrzyma 0,0 z powodu: [1] Kiedy screenOrientationjest domyślnie, hasPermanentMenuKeyjest falsetylko wtedy, gdy nie jest obracany. [2] Kiedy screenOrientationjest krajobraz, to wpada w displayMetrics.heightPixels != realDisplayMetrics.heightPixels)inny. [3] Kiedy screenOrientationjest portret, hasPermanentMenuKeyjest false.
Owoce

działa, ale na 4.x, jeśli (hasPermanentMenuKey) jest marnotrawstwem, skomentuj to
djdance

Twój stan powinien być następujący: (Build.VERSION.SDK_INT> = Build.VERSION_CODES.JELLY_BEAN_MR1). Interfejs API 17 jest wymagany, aby wykonać publiczne void getRealMetrics (DisplayMetrics outMetrics). Zobacz dokumentację: developer.android.com/reference/kotlin/android/util/…
portfoliobuilder

1

Łącząc odpowiedź od @egis i innych - działa to dobrze na różnych urządzeniach, testowane na Pixel EMU, Samsung S6, Sony Z3, Nexus 4. Ten kod wykorzystuje wymiary wyświetlacza do testowania dostępności paska nawigacyjnego, a następnie używa rzeczywistego rozmiar paska nawigacyjnego systemu, jeśli jest obecny.

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

}


Jeśli chcę programowo ustawić wysokość TextView na dole użytecznej wysokości ekranu, jak użyłbym tej klasy?
Naveed Abbas

1

Jak uzyskać wysokość paska nawigacji i paska stanu. Ten kod działa dla mnie na niektórych urządzeniach Huawei i urządzeniach Samsung . Powyższe rozwiązanie Egis jest dobre, jednak na niektórych urządzeniach nadal jest niepoprawne. Więc poprawiłem to.

To jest kod, aby uzyskać wysokość paska stanu

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
    }

Ta metoda zawsze zwraca wysokość paska nawigacji, nawet jeśli pasek nawigacji jest ukryty.

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

UWAGA: w Samsung A70 ta metoda zwraca wysokość paska stanu + wysokość paska nawigacji. Na innych urządzeniach (Huawei) zwraca tylko wysokość paska nawigacji i zwraca 0, gdy pasek nawigacji jest ukryty.

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

To jest kod do uzyskania wysokości paska nawigacji i paska stanu

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
                }

1

Zrobiłem to, działa na każdym testowanym urządzeniu, a nawet na emulatorach:

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

0

Oto jak to rozwiązałem. Zrobiłem chowany dolny pasek, który wymagał wypełnienia w zależności od tego, czy był pasek nawigacyjny, czy nie (pojemnościowy, ekranowy lub po prostu przed lizakiem).


Widok

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

0

W moim przypadku, gdy chciałem mieć coś takiego:

Zrzut ekranu

Musiałem postępować zgodnie z tym samym, co sugerował @Mdlc, ale prawdopodobnie nieco prostszy (kierowanie tylko > = 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)

Działa również w krajobrazie:

krajobraz

Edytuj Powyższe rozwiązanie nie działa poprawnie w trybie wielu okien, gdzie użyteczny prostokąt nie jest mniejszy tylko ze względu na pasek nawigacji, ale także z powodu niestandardowego rozmiaru okna. Jedną z rzeczy, które zauważyłem, jest to, że w wielu oknach pasek nawigacji nie znajduje się nad aplikacją, więc nawet bez zmian w wypełnieniu DecorView mamy prawidłowe zachowanie:

Wiele okien bez zmian w wypełnieniu widoku dekoru Jedno okno bez zmian w wypełnieniu widoku dekoru

Zwróć uwagę na różnicę między sposobem, w jaki pasek nawigacji znajduje się na dole aplikacji w tych scenariuszach. Na szczęście jest to łatwe do naprawienia. Możemy sprawdzić, czy aplikacja obsługuje wiele okien. Poniższy kod zawiera również część do obliczania i dostosowywania pozycji paska narzędzi (pełne rozwiązanie: 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
    }

Wynik w trybie wyskakującym Samsunga:

wprowadź opis obrazu tutaj


0

Testowany kod do pobierania wysokości paska nawigacji (w pikselach):

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

Testowany kod do pobierania wysokości paska stanu (w pikselach):

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

Konwersja pikseli na dp:

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

0

W przypadku Samsunga S8 żadna z powyższych metod nie dawała odpowiedniej wysokości paska nawigacji, więc użyłem androida dostawcy wysokości klawiatury KeyboardHeightProvider . I dało mi wzrost w wartościach ujemnych, a dla mojego pozycjonowania układu dostosowałem tę wartość w obliczeniach.

Oto 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"/>

Wykorzystanie w 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()
    }
}

Aby uzyskać dalszą pomoc, możesz rzucić okiem na zaawansowane wykorzystanie tego tutaj .


0

Proste rozwiązanie jednowierszowe

Jak sugerowano na przykład w wielu powyższych odpowiedziach

Samo pobranie wysokości paska nawigacji może nie wystarczyć. Musimy rozważyć, czy 1. istnieje pasek nawigacji, 2. czy jest na dole, czy po prawej czy lewej stronie, 3. czy aplikacja jest otwarta w trybie wielu okien.

Na szczęście możesz łatwo ominąć całe długie kodowanie, po prostu ustawiając android:fitsSystemWindows="true"układ główny. System Android automatycznie zajmie się dodaniem niezbędnego wypełnienia do układu głównego, aby upewnić się, że widoki podrzędne nie trafiają do paska nawigacji lub regionów paska stanu.

Istnieje proste rozwiązanie jednokreskowe

android:fitsSystemWindows="true"

lub programowo

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

możesz również uzyskać widok roota przez

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

Aby uzyskać więcej informacji na temat uzyskiwania widoku głównego, zobacz - https://stackoverflow.com/a/4488149/9640177

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.