Como obtenho a altura e a largura da Barra de Navegação do Android de maneira programática?

122

A barra de navegação preta na parte inferior da tela não é facilmente removível no Android. Faz parte do Android desde 3.0 como um substituto para botões de hardware. Aqui está uma foto:

Barra de Sistema

Como posso obter o tamanho da largura e da altura desse elemento da IU em pixels?

Kevik
fonte
A melhor solução para este problema é adicionada aqui. Podemos identificar a barra de navegação presente no dispositivo, comparando métricas de exibição e métricas reais. Olha minha resposta, eu adicionei o código completo para descobrir o tamanho real da barra de navegação para dispositivos Android inteiros.
Sino Raj

Respostas:

178

Experimente o código abaixo:

Resources resources = context.getResources();
int resourceId = resources.getIdentifier("navigation_bar_height", "dimen", "android");
if (resourceId > 0) {
    return resources.getDimensionPixelSize(resourceId);
}
return 0;
Sanket
fonte
7
Obrigado. +1. Você sabe o quão estável é essa abordagem? Os identificadores de recursos estarão sujeitos a alterações nas diferentes versões da plataforma?
Ben Pearson
59
Observe que este trecho de código não retorna 0 em dispositivos sem barra de navegação. Testado em Samsung S2 e S3. Eu tenho 72 e 96.
Egis
4
@Egidijus Veja minha resposta, ele retornará 0 para dispositivos que possuem navegação física stackoverflow.com/a/29938139/1683141
Mdlc
1
galaxy s6 edge também não retorna 0, embora não haja barra de navegação na parte inferior. retorna 192.
agamov
5
Este código mostra o tamanho padrão da barra de navegação (tente também navigation_bar_height_landscapepara a altura da barra de navegação no modo paisagem e navigation_bar_widthpara a largura de uma barra de navegação vertical). Você tem que descobrir separadamente se e onde a barra de navegação está realmente sendo exibida, por exemplo, testando a presença de um botão de menu físico. Talvez você possa encontrar alguma outra maneira no código de
software
103

Eu obtenho o tamanho da barra de navegação comparando o tamanho da tela utilizável pelo aplicativo com o tamanho real da tela. Presumo que a barra de navegação esteja presente quando o tamanho da tela utilizável pelo aplicativo é menor do que o tamanho da tela real. Então calculo o tamanho da barra de navegação. Este método funciona com API 14 e superior.

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

ATUALIZAR

Para uma solução que leva em conta os cortes de exibição, verifique a resposta de John .

Egis
fonte
2
Esta é a única solução que funciona para mim em todos os diferentes dispositivos que tenho testado. Minha necessidade é determinar se a barra de navegação está na parte inferior da tela, levando em consideração a orientação. Ajustei levemente o código na resposta para apenas retornar o tamanho da barra de navegação na parte inferior da tela, 0 indica que não está presente. Meu teste mostra que Nexus5 = retrato: 144, Paisagem: 0 | Nexus7 = retrato: 96, Paisagem: 96 | Samsung Note II = retrato: 0, Paisagem: 0 | Samsung S10 = retrato: 0, Paisagem: 0
se22as
2
Também posso confirmar que a solução de Danylo funciona apenas em alguns modelos. Esta solução da Egidijus é a melhor solução de trabalho e funcionou para todos os modelos testados até agora. Obrigado por isso.
Bisclavret
2
A barra de status faz parte da tela utilizável do aplicativo.
Egis
1
Isso funciona, mas não leva em consideração se a barra existente está oculta ou não. Alguma ideia de como verificar isso?
X-HuMan
1
Ele não está funcionando para Android P enquanto o gesto de navegação está ativado em vez da barra de navegação. Você pode me orientar como fazer resolvido para esta situação
Nik
36

A altura da NavigationBar varia para alguns dispositivos, mas também para algumas orientações. Primeiro você tem que verificar se o dispositivo possui uma barra de navegação, depois se o dispositivo é um tablet ou não (telefone) e, por último, você deve olhar a orientação do dispositivo para obter a altura correta.

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
fonte
Isso não funciona para mim, Nexus5 retorna true para hasMenuKey e hasBackKey, portanto, ele pensa que não há navBar. (Nexus7 retorna corretamente falso para hasMenuKey e hasBackKey)
se22as
5
Testei esse código no Nexus 5 e ele retorna falso para ambos. Tem certeza de que não está em um emulador ou rom?
Mdlc de
Sim, eu estava usando um emulador, desculpe, eu deveria ter esclarecido em minha declaração anterior
se22as
não deveria ser! hasMenuKey || ! hasBackKey em vez de! hasMenuKey &&! hasBackKey? por favor, verifique
yongsunCN
2
Infelizmente, não funciona no Sony Xperia Z3, hasBackKey = false!embora devesse ser verdade. Em vez disso, funcionou o seguinte: boolean navBarExists = getResources().getBoolean(getResources().getIdentifier("config_showNavigationBar", "bool", "android"));
Hamzeh Soboh
27

Na verdade, a barra de navegação em tablets (pelo menos Nexus 7) tem tamanhos diferentes em retrato e paisagem, então esta função deve ser assim:

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
fonte
6
Para recuperar a orientação do contexto, usecontext.getResources().getConfiguration().orientation
Hugo Gresse
A melhor solução para este problema é adicionada aqui. Podemos identificar a barra de navegação presente no dispositivo, comparando métricas de exibição e métricas reais. Olha minha resposta, eu adicionei o código completo para descobrir o tamanho real da barra de navegação para dispositivos Android inteiros.
Sino Raj
17

Acho que a melhor resposta está aqui porque permite que você obtenha uma altura de recorte uniforme também.

Obtenha sua visualização raiz e adicione setOnApplyWindowInsetsListener (ou você pode substituir onApplyWindowInsets a partir dela) e obter insets.getSystemWindowInsets a partir dela.

Na minha atividade de câmera, adiciono preenchimento igual ao systemWindowInsetBottom ao meu layout inferior. E, finalmente, corrigiu o problema de recorte.

Inserções de atividade da câmera

com appcompat é assim

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

sem appcompat, isto:

mCameraSourcePreview.setOnApplyWindowInsetsListener((v, insets) -> { ... })
John
fonte
É possível usar em setOnApplyWindowInsetsListenervez disso? Se sim, como? e por que você diferenciou entre LOLLIPOP e o resto?
desenvolvedor Android de
sim, é possível e é melhor. eu vou consertar a resposta. e diferenciar não precisa também
John
1
Esta é a melhor resposta. Eu literalmente te amo.
Himanshu Rawat
1
Esta é a verdadeira resposta.
nyconing
1
O ouvinte nunca é executado quando eu chamo isso de onCreate (). Estou esquecendo de algo?
Howettl
12

Espero que isso ajude você

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
fonte
5

Este é meu código para adicionar paddingRight e paddingBottom a uma visualização para evitar a barra de navegação. Combinei algumas das respostas aqui e criei uma cláusula especial para orientação paisagem juntamente com isInMultiWindowMode. A chave é ler navigation_bar_height , mas também verificar config_showNavigationBar para ter certeza de que devemos realmente usar a altura.

Nenhuma das soluções anteriores funcionou para mim. A partir do Android 7.0, você deve levar em consideração o modo de janela múltipla. Isso quebra as implementações comparando display.realSize com display.size, pois realSize fornece as dimensões de toda a tela (ambas as janelas divididas) e size apenas fornece as dimensões da janela do seu aplicativo. Definir o preenchimento para essa diferença deixará toda a sua visualização sendo preenchida.

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

A altura da barra de navegação inferior é 48 dp (nos modos retrato e paisagem) e é 42 dp quando a barra é colocada verticalmente.

Aritra Roy
fonte
4
A altura é NORMALMENTE 48 dp. Mas alguns fabricantes podem mudar isso, alguma ROM personalizada pode mudar isso, e assim por diante. É melhor confiar na resposta de Danylo ( stackoverflow.com/a/26118045/1377145 ) para ter o tamanho exato
Hugo Gresse
Isso é ainda melhor. Obrigado.
Aritra Roy
colocado verticalmente? como quando está no modo paisagem?
sudocoder
1

A solução proposta por Egidijus e funciona perfeitamente para Build.VERSION.SDK_INT> = 17

Mas recebi "NoSuchMethodException" durante a execução da seguinte instrução com Build.VERSION.SDK_INT <17 no meu dispositivo:

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

Modifiquei o método getRealScreenSize () para esses casos:

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

Resolvi esse problema para todos os dispositivos (incluindo Nexus 5, Samsung Galaxy Nexus 6 edge +, Samsung S10, Samsung Note II etc.). Acho que isso o ajudará a lidar com problemas relacionados ao dispositivo.

Aqui estou adicionando dois 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;
    }
}

E código C # (para Formulários Xamarin / 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
fonte
Display.getRealMetrics()requer API de nível 17
Weizhi
if (Build.VERSION.SdkInt> = BuildVersionCodes.JellyBeanMr2) já verificado.
Sino Raj
O código precisa ser corrigido. Meu LG Nexus 5X obterá 0,0 devido a: [1] Quando screenOrientationé o padrão, hasPermanentMenuKeyé falsesomente se não girar. [2] Quando screenOrientationé paisagem, cai em displayMetrics.heightPixels != realDisplayMetrics.heightPixels)outra coisa. [3] Quando screenOrientationé retrato, hasPermanentMenuKeyé false.
Frutas
funciona, mas no 4.x se (hasPermanentMenuKey) for desperdiçado, comente
djdance
Sua condição deve ser se (Build.VERSION.SDK_INT> = Build.VERSION_CODES.JELLY_BEAN_MR1). A API 17 é necessária para fazer public void getRealMetrics (DisplayMetrics outMetrics). Consulte os documentos: developer.android.com/reference/kotlin/android/util/…
portfoliobuilder
1

Combinando a resposta de @egis e outros - funciona bem em uma variedade de dispositivos, testados em Pixel EMU, Samsung S6, Sony Z3, Nexus 4. Este código usa as dimensões da tela para testar a disponibilidade da barra de navegação e, em seguida, usa a barra de navegação real tamanho da barra de navegação do sistema, se houver.

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

}

Homem malvado
fonte
Se eu quiser definir programaticamente a altura de um TextView para a parte inferior da altura utilizável da tela, como usaria essa classe?
Naveed Abbas
1

Como obter a altura da barra de navegação e da barra de status. Este código funciona para mim em alguns dispositivos Huawei e Samsung . A solução da Egis acima é boa, no entanto, ainda é incorreta em alguns dispositivos. Então, eu melhorei.

Este é um código para obter a altura da barra de status

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 sempre retorna a altura da barra de navegação, mesmo quando a barra de navegação 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: no Samsung A70, este método retorna a altura da barra de status + altura da barra de navegação. Em outros dispositivos (Huawei), ele só retorna a altura da barra de navegação e retorna 0 quando a barra de navegação 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 é o código para obter a altura da barra de navegação e da barra de status

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

Já fiz isso, funciona em todos os dispositivos que testei e até mesmo em 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
fonte
0

Aqui está como resolvi isso. Fiz uma barra inferior escondível que precisava de preenchimento dependendo se havia uma barra de navegação ou não (capacitiva, na tela ou apenas pré-pirulito).


Visão

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

No meu caso, eu queria ter algo assim:

Captura de tela

Tive que seguir a mesma coisa sugerida por @Mdlc, mas provavelmente um pouco mais simples (segmentação apenas > = 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)

Funciona também na paisagem:

panorama

Editar A solução acima não funciona corretamente no modo de janela múltipla, onde o retângulo utilizável não é menor apenas devido à barra de navegação, mas também devido ao tamanho personalizado da janela. Uma coisa que percebi é que em janelas múltiplas, a barra de navegação não fica pairando sobre o aplicativo, então, mesmo sem alterações no preenchimento de DecorView, temos o comportamento correto:

Multi-janela sem alterações no preenchimento da visualização da decoração Janela única sem alterações no preenchimento da visualização da decoração

Observe a diferença entre como a barra de navegação está pairando sobre a parte inferior do aplicativo nesses cenários. Felizmente, isso é fácil de corrigir. Podemos verificar se o aplicativo é multi-janela. O código abaixo também inclui a parte para calcular e ajustar a posição da barra de ferramentas (solução 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 no modo popup Samsung:

insira a descrição da imagem aqui

Bakhshi
fonte
0

Código testado para obter a altura da barra de navegação (em pixels):

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 testado para obter a altura da barra de status (em pixels):

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

Convertendo pixels em dp:

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

No caso do Samsung S8, nenhum dos métodos fornecidos acima fornecia a altura adequada da barra de navegação, então usei o android, provedor de altura de teclado KeyboardHeightProvider . E me deu altura em valores negativos e para o meu posicionamento de layout ajustei esse valor nos cálculos.

Aqui está 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 em 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 qualquer ajuda adicional, você pode dar uma olhada no uso avançado disso aqui .

Nilesh Deokar
fonte
0

Solução Simples de Uma Linha

Como sugerido em muitas das respostas acima, por exemplo

Simplesmente obter a altura da barra de navegação pode não ser suficiente. Precisamos considerar se 1. a barra de navegação existe, 2. está na parte inferior, direita ou esquerda, 3. é o aplicativo aberto no modo de janela múltipla.

Felizmente, você pode facilmente ignorar toda a codificação longa simplesmente configurando android:fitsSystemWindows="true"em seu layout de raiz. O sistema Android cuidará automaticamente de adicionar o preenchimento necessário ao layout raiz para garantir que as visualizações filho não entrem na barra de navegação ou nas regiões da barra de status.

Existe uma solução simples de uma linha

android:fitsSystemWindows="true"

ou programaticamente

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

você também pode obter a visão raiz por

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

Para obter mais detalhes sobre como obter a visualização root, consulte - https://stackoverflow.com/a/4488149/9640177

mayank1513
fonte