¿Cómo obtengo la altura y el ancho de la barra de navegación de Android mediante programación?

122

La barra de navegación negra en la parte inferior de la pantalla no se puede quitar fácilmente en Android. Ha sido parte de Android desde 3.0 como reemplazo de botones de hardware. Aquí hay una imagen:

Barra de sistema

¿Cómo puedo obtener el tamaño del ancho y el alto de este elemento de la interfaz de usuario en píxeles?

Kevik
fuente
Aquí se agrega la mejor solución para este problema. Podemos identificar si la barra de navegación está presente en el dispositivo comparando métricas de visualización y métricas reales. Mira mi respuesta, agregué el código completo para averiguar el tamaño real de la barra de navegación para dispositivos Android completos.
Sino Raj

Respuestas:

178

Pruebe el siguiente código:

Resources resources = context.getResources();
int resourceId = resources.getIdentifier("navigation_bar_height", "dimen", "android");
if (resourceId > 0) {
    return resources.getDimensionPixelSize(resourceId);
}
return 0;
Sanket
fuente
7
Gracias. +1. ¿Sabes cuán estable es este enfoque? ¿Los identificadores de recursos estarán sujetos a cambios en las diferentes versiones de la plataforma?
Ben Pearson
59
Tenga en cuenta que este fragmento de código no devuelve 0 en dispositivos sin barra de navegación. Probado en Samsung S2 y S3. Obtuve 72 y 96.
Egis
4
@Egidijus Mira mi respuesta, devolverá 0 para los dispositivos que tienen navegación física stackoverflow.com/a/29938139/1683141
Mdlc
1
galaxy s6 edge tampoco devuelve 0, aunque no hay una barra de navegación en la parte inferior. devuelve 192.
agamov
5
Este código muestra el tamaño predeterminado de la barra de navegación (pruebe también navigation_bar_height_landscapepara la altura de la barra de navegación en modo horizontal y navigation_bar_widthpara el ancho de una barra de navegación vertical). Debe averiguar por separado si se muestra realmente la barra de navegación y dónde se muestra, por ejemplo, probando la presencia de un botón de menú físico. Quizás pueda encontrar alguna otra forma en el código fuente de Android en android.googlesource.com/platform/frameworks/base/+/…
user149408
103

Obtengo el tamaño de la barra de navegación al comparar el tamaño de pantalla utilizable por la aplicación con el tamaño de pantalla real. Supongo que la barra de navegación está presente cuando el tamaño de la pantalla utilizable por la aplicación es menor que el tamaño real de la pantalla. Luego calculo el tamaño de la barra de navegación. Este método funciona con API 14 y posteriores.

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

ACTUALIZAR

Para obtener una solución que tenga en cuenta los recortes de pantalla, consulte la respuesta de John .

Égida
fuente
2
Esta es la única solución que me funciona en todos los dispositivos que he estado probando. Mi requisito es determinar si la barra de navegación está en la parte inferior de la pantalla teniendo en cuenta la orientación. He modificado ligeramente el código en la respuesta para que me devuelva el tamaño de la barra de navegación en la parte inferior de la pantalla, 0 indica que no está presente. Mis pruebas muestran que Nexus5 = retrato: 144, paisaje: 0 | Nexus7 = retrato: 96, paisaje: 96 | Samsung Note II = retrato: 0, paisaje: 0 | Samsung S10 = retrato: 0, paisaje: 0
se22as
2
También puedo confirmar que la solución de Danylo funciona solo en algunos modelos. Esta solución de Egidijus es una solución de trabajo mejor y ha funcionado para todos los modelos probados hasta ahora. Gracias por esto.
Bisclavret
2
La barra de estado es parte de la pantalla utilizable de la aplicación.
Egis
1
Esto funciona, pero no considera si la barra existente está oculta o no. ¿Alguna idea de cómo comprobar esto?
X-HuMan
1
No funciona para Android P mientras Gestos de navegación está activado en lugar de la barra de navegación. ¿Puede guiarme por favor sobre cómo resolver esta situación?
Nik
36

La altura de la barra de navegación varía para algunos dispositivos, pero también para algunas orientaciones. Primero debe verificar si el dispositivo tiene una barra de navegación, luego si el dispositivo es una tableta o no una tableta (teléfono) y finalmente debe mirar la orientación del dispositivo para obtener la altura correcta.

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;
}
Mdlc
fuente
Esto no funciona para mí, Nexus5 devuelve verdadero para hasMenuKey y hasBackKey, por lo que cree que no hay navBar. (Nexus7 devuelve correctamente falso para hasMenuKey y hasBackKey)
se22as
5
Probé este código en el Nexus 5 y devuelve falso para ambos. ¿Estás seguro de que no estás en un emulador o rom?
Mdlc
Sí, estaba usando un emulador, lo siento, debería haberlo aclarado en mi declaración anteriormente
se22as
¿No debería ser esto? hasMenuKey || ! hasBackKey en lugar de! hasMenuKey &&! hasBackKey? por favor verifique
yongsunCN
2
Desafortunadamente, no funciona en Sony Xperia Z3, hasBackKey = false!aunque debería ser cierto. Lo siguiente funcionó en su lugar: boolean navBarExists = getResources().getBoolean(getResources().getIdentifier("config_showNavigationBar", "bool", "android"));
Hamzeh Soboh
27

En realidad, la barra de navegación de las tabletas (al menos Nexus 7) tiene un tamaño diferente en vertical y horizontal, por lo que esta función debería verse así:

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
fuente
6
Para recuperar la orientación del contexto, usecontext.getResources().getConfiguration().orientation
Hugo Gresse
Aquí se agrega la mejor solución para este problema. Podemos identificar si la barra de navegación está presente en el dispositivo comparando métricas de visualización y métricas reales. Mira mi respuesta, agregué el código completo para averiguar el tamaño real de la barra de navegación para dispositivos Android completos.
Sino Raj
17

Creo que la mejor respuesta está aquí porque también te permite obtener una altura de corte uniforme.

Tome su vista raíz y agregue setOnApplyWindowInsetsListener (o puede anular onApplyWindowInsets de ella), y tome insets.getSystemWindowInsets de ella.

En la actividad de mi cámara, agrego un relleno igual al systemWindowInsetBottom a mi diseño inferior. Y finalmente, soluciona el problema del recorte.

Insertos de actividad de la cámara

con appcompat es así

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

sin appcompat, esto:

mCameraSourcePreview.setOnApplyWindowInsetsListener((v, insets) -> { ... })
Juan
fuente
¿Es posible usarlo setOnApplyWindowInsetsListeneren su lugar? ¿Si es así, cómo? y ¿por qué diferenciaste entre LOLLIPOP y el resto?
desarrollador de Android
sí, es posible y es mejor. Arreglaré la respuesta. y diferenciar no es necesario también
Juan
1
Esta es la mejor respuesta. Literalmente te amo
Himanshu Rawat
1
Ésta es la verdadera respuesta.
nyconing
1
El oyente nunca se ejecuta cuando llamo a esto desde onCreate (). ¿Me estoy perdiendo de algo?
howettl
12

Espero que esto te ayude

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;
}
Francisco Drumond
fuente
5

Este es mi código para agregar paddingRight y paddingBottom a una vista para esquivar la barra de navegación. Combiné algunas de las respuestas aquí e hice una cláusula especial para la orientación horizontal junto con isInMultiWindowMode. La clave es leer navigation_bar_height , pero también verificar config_showNavigationBar para asegurarse de que realmente deberíamos usar la altura.

Ninguna de las soluciones anteriores funcionó para mí. A partir de Android 7.0, debe tener en cuenta el modo de ventana múltiple. Esto rompe las implementaciones que comparan display.realSize con display.size ya que realSize te da las dimensiones de toda la pantalla (ambas ventanas divididas) y size solo te da las dimensiones de la ventana de tu aplicación. Configurar el relleno a esta diferencia dejará toda la vista como relleno.

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

La altura de la barra de navegación inferior es 48 dp (tanto en modo vertical como horizontal) y 42 dp cuando la barra se coloca verticalmente.

Aritra Roy
fuente
4
La altura es NORMALMENTE 48dp. Pero algún fabricante puede cambiar eso, alguna ROM personalizada puede cambiar eso, y así sucesivamente. Es mejor confiar en la respuesta de Danylo ( stackoverflow.com/a/26118045/1377145 ) para tener el tamaño exacto
Hugo Gresse
Eso es incluso mejor. Gracias.
Aritra Roy
colocado verticalmente? como cuando está en modo paisaje?
sudocoder
1

La solución propuesta por Egidijus y funciona perfectamente para Build.VERSION.SDK_INT> = 17

Pero obtuve "NoSuchMethodException" durante la ejecución de la siguiente declaración con Build.VERSION.SDK_INT <17 en mi dispositivo:

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

He modificado el método getRealScreenSize () para tales casos:

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

Resolví este problema para todos los dispositivos (incluidos Nexus 5, Samsung Galaxy Nexus 6 edge +, Samsung S10, Samsung Note II, etc.). Creo que esto le ayudará a manejar los problemas que dependen del dispositivo.

Aquí estoy agregando dos tipos de códigos,

Código Java (para Android nativo):

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

Y código C # (para 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
fuente
Display.getRealMetrics()requiere nivel de API 17
Weizhi
si (Build.VERSION.SdkInt> = BuildVersionCodes.JellyBeanMr2) ya está marcado.
Sino Raj
El código debe arreglarse. Mi LG Nexus 5X obtendrá 0.0 debido a: [1] Cuando screenOrientationestá predeterminado, hasPermanentMenuKeyes falsesolo si no gira. [2] Cuando screenOrientationes un paisaje, cae en displayMetrics.heightPixels != realDisplayMetrics.heightPixels)otro. [3] Cuando screenOrientationes retrato, hasPermanentMenuKeyes false.
Fruta
funciona, pero en 4.x si (hasPermanentMenuKey) es un desperdicio,
coméntelo
Su condición debería ser si (Build.VERSION.SDK_INT> = Build.VERSION_CODES.JELLY_BEAN_MR1). Se requiere la API 17 para hacer público vacío getRealMetrics (DisplayMetrics outMetrics). Ver documentos: developer.android.com/reference/kotlin/android/util/…
portfoliobuilder
1

Combinando la respuesta de @egis y otros, esto funciona bien en una variedad de dispositivos, probado en Pixel EMU, Samsung S6, Sony Z3, Nexus 4. Este código usa las dimensiones de la pantalla para probar la disponibilidad de la barra de navegación y luego usa las tamaño de la barra de navegación del sistema, si está presente.

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

}

Hombre malo
fuente
Si quiero establecer mediante programación la altura de un TextView en la parte inferior de la altura utilizable de la pantalla, ¿cómo usaría esta clase?
Naveed Abbas
1

Cómo obtener la altura de la barra de navegación y la barra de estado. Este código me funciona en algunos dispositivos Huawei y Samsung . La solución de Egis anterior es buena, sin embargo, todavía es incorrecta en algunos dispositivos. Entonces, lo mejoré.

Este es el código para obtener la altura de la barra de estado.

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
    }

Este método siempre devuelve la altura de la barra de navegación incluso cuando la barra de navegación está oculta.

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

NOTA: en Samsung A70, este método devuelve la altura de la barra de estado + la altura de la barra de navegación. En otros dispositivos (Huawei), solo devuelve la altura de la barra de navegación y devuelve 0 cuando la barra de navegación está oculta.

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

Este es el código para obtener la altura de la barra de navegación y la barra de estado

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
fuente
1

He hecho esto, funciona en todos los dispositivos que probé, e incluso en emuladores:

// 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
fuente
0

Así es como resolví esto. Hice una barra inferior ocultable que necesitaba relleno dependiendo de si había una barra de navegación o no (capacitiva, en pantalla o simplemente antes de la paleta).


Ver

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
fuente
0

En mi caso en el que quería tener algo como esto:

Captura de pantalla

Tuve que seguir lo mismo que sugirió @Mdlc pero probablemente un poco más simple (apuntando solo > = 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)

También funciona en el paisaje:

paisaje

Editar La solución anterior no funciona correctamente en el modo de ventanas múltiples donde el rectángulo utilizable no es más pequeño solo debido a la barra de navegación sino también debido al tamaño de ventana personalizado. Una cosa que noté es que en múltiples ventanas, la barra de navegación no se cierne sobre la aplicación, por lo que incluso sin cambios en el relleno de DecorView, tenemos el comportamiento correcto:

Ventana múltiple sin cambios en el relleno de la vista de decoración Ventana única sin cambios en el relleno de la vista de decoración

Tenga en cuenta la diferencia entre cómo la barra de navegación se desplaza sobre la parte inferior de la aplicación en estos escenarios. Afortunadamente, esto es fácil de solucionar. Podemos comprobar si la aplicación es multiventana. El siguiente código también incluye la parte para calcular y ajustar la posición de la barra de herramientas (solución completa: 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
    }

Resultado en el modo emergente de Samsung:

ingrese la descripción de la imagen aquí

Bakhshi
fuente
0

Código probado para obtener la altura de la barra de navegación (en píxeles):

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

Código probado para obtener la altura de la barra de estado (en píxeles):

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

Conversión de píxeles a dp:

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

En el caso de Samsung S8, ninguno de los métodos proporcionados anteriormente proporcionaba la altura adecuada de la barra de navegación, así que utilicé el proveedor de altura de teclado KeyboardHeightProvider android . Y me dio altura en valores negativos y para el posicionamiento de mi diseño ajusté ese valor en los cálculos.

Aqui esta 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"/>

Uso en 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()
    }
}

Para obtener más ayuda, puede echar un vistazo al uso avanzado de esto aquí .

Nilesh Deokar
fuente
0

Solución simple de una línea

Como se sugiere en muchas de las respuestas anteriores, por ejemplo

Es posible que simplemente obtener la altura de la barra de navegación no sea suficiente. Necesitamos considerar si 1. existe una barra de navegación, 2. está en la parte inferior, derecha o izquierda, 3. si la aplicación está abierta en modo de múltiples ventanas.

Afortunadamente, puede omitir fácilmente toda la codificación larga simplemente configurando android:fitsSystemWindows="true"su diseño raíz. El sistema Android se encargará automáticamente de agregar el relleno necesario al diseño raíz para asegurarse de que las vistas secundarias no entren en la barra de navegación o en las regiones de la barra de estado.

Hay una solución simple de una línea

android:fitsSystemWindows="true"

o programáticamente

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

también puede obtener la vista raíz por

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

Para obtener más detalles sobre cómo obtener la vista raíz, consulte: https://stackoverflow.com/a/4488149/9640177

mayank1513
fuente