Fórmula px para dp, dp para px android


150

Estou tentando calcular uma quantidade variável de pixels para densificar pixels independentes e vice-versa.

Essa fórmula (px to dp): dp = (int)(px / (displayMetrics.densityDpi / 160));não funciona em dispositivos pequenos porque é dividida por zero.

Esta é minha fórmula de dp para px:

px = (int)(dp * (displayMetrics.densityDpi / 160));

Alguém poderia me dar algumas dicas?


1
Convertendo unidades dp para unidades de pixel developer.android.com/guide/practices/...
Padma Kumar

@Ram: Acho que sua fórmula está ótima. Como você obterá uma divisão por zero? O displayMetrics.densityDpi será 120, 160, 240 ou 320, nunca 0.
ct_rob

Eu concordo com @ct_rob. O valor mínimo do displayMetrics.densityDpi / 160 será 0,75. Você deve ter transmitido para int no local incorreto.
TomTaila

Respostas:


318

Nota: A solução amplamente usada acima é baseada em displayMetrics.density. No entanto, os documentos explicam que esse valor é um valor arredondado, usado com a tela 'buckets'. Por exemplo. no meu Nexus 10, ele retorna 2, onde o valor real seria 298dpi (real) / 160dpi (padrão) = 1,8625.

Dependendo dos seus requisitos, você pode precisar da transformação exata, que pode ser alcançada assim:

[Editar] Isso não deve ser misturado com a unidade de DP interna do Android, pois isso ainda é baseado nos baldes de tela. Use-o onde desejar uma unidade que renderize o mesmo tamanho real em diferentes dispositivos.

Converter dp em pixel:

public int dpToPx(int dp) {
    DisplayMetrics displayMetrics = getContext().getResources().getDisplayMetrics();
    return Math.round(dp * (displayMetrics.xdpi / DisplayMetrics.DENSITY_DEFAULT));     
}

Converter pixel em dp:

public int pxToDp(int px) {
    DisplayMetrics displayMetrics = getContext().getResources().getDisplayMetrics();
    return Math.round(px / (displayMetrics.xdpi / DisplayMetrics.DENSITY_DEFAULT));
}

Observe que existem propriedades xdpi e ydpi, você pode querer distinguir, mas não consigo imaginar uma exibição sadia em que esses valores sejam muito diferentes.


8
Isso não calculará o valor correto para dp / px em muitos dispositivos (incluindo o Nexus 10)! Como você diz displayMetrics.density, é arredondado para o intervalo de tela mais próximo, mas a unidade dp! Tente desenhar um objeto com 160dp de largura e logo abaixo dele, você desenha outro objeto com dpToPx (160) pixels de largura e verá que o tamanho dos dois objetos é diferente. Além disso, alguns telefones (como o Galaxy Mini e o Galaxy S3 Mini) relatam valores completamente errados para xdpi / ydpi; portanto, nesses telefones, seus métodos retornam resultados completamente errados.
Nibarius

@nibarius Sim, você não pode misturar os cálculos de dp em caixa do Android com os itens acima. A solução acima é entendida como um valor independente da densidade separada, com base na física exata do dispositivo. Necessário, por exemplo, onde você deseja mostrar uma linha exatamente o mesmo comprimento real em diferentes dispositivos. Obviamente, se as entradas xdpi / ydpi não forem definidas corretamente por alguns dispositivos, elas não funcionarão lá.
Bachi

1
xdpi e ydpi não devem ser usados, porque são imprecisos em muitos dispositivos, às vezes muito. Somente o DisplayMetrics.densityDpi é confiável, o que é lamentável, pois é impreciso por design. Consulte o tópico do fórum do Google para obter mais informações: groups.google.com/forum/#!topic/android-developers/g56jV0Hora0
Mark McClelland

1
Encontrei essa resposta e, na verdade, usei muitas soluções semelhantes, mas apenas analisei os documentos e encontrei getDimensionPixelOffSet com uma determinada dimensão (declarada em dip / dp) que retorna o deslocamento em pixels, assim como o código artesanal. Eu testei e trabalhei na perfeição. Espero que ajude!
william Gouvêa

1
Mehhh, isso não dá o valor correto. Tente: Recursos r = getResources (); px flutuante = TypedValue.applyDimension (TypedValue.COMPLEX_UNIT_DIP, 14, r.getDisplayMetrics ()); Conforme descrito aqui: stackoverflow.com/questions/4605527/converting-pixels-to-dp
Rik van Velzen

103

Resolvi meu problema usando as seguintes fórmulas. Que outras pessoas possam se beneficiar disso.

dp para px:

displayMetrics = context.getResources().getDisplayMetrics();
return (int)((dp * displayMetrics.density) + 0.5);

px para dp:

displayMetrics = context.getResources().getDisplayMetrics();
return (int) ((px/displayMetrics.density)+0.5);

27
@Vame A adição de 0,5 é usada para arredondar para CIMA até o valor inteiro mais próximo. A 0,5 é adicionada e o resultado do cálculo é convertido como int, fazendo com que trunque a mantissa e deixe a característica como um valor inteiro adequadamente arredondado.
22612 Kelly Copley

3
Isso nem sempre é correto. Quando tenho dois layouts, um dentro do outro, arredondo os cantos de cada visualização (um usando dp, outro convertendo em dp).
Marek

Não tive problemas com essa técnica. Usei isso para diferentes layouts e sempre funcionou como esperado. Claro que usei isso alguns anos atrás e não tenho certeza se ainda funciona. Talvez você possa tentar a outra técnica na resposta do PanaVTEC. Também é possível que exista mais nos cantos arredondados do que apenas cálculos de dp / px.
Bram

5
É a mesma solução adotada no site do desenvolvedor do Android, então acho que está correta :). http://developer.android.com/guide/practices/screens_support.html#dips-pels
alocaly

1
+1 Obrigado cara. Trabalhou como um encanto! Obrigado por compartilhar esta solução conosco.
Simon Dorociak

34

px para dp:

int valueInpx = ...;
int valueInDp= (int) TypedValue.applyDimension(
            TypedValue.COMPLEX_UNIT_DIP, valueInpx , getResources()
                .getDisplayMetrics());

7
Isto é de DP para PX, mas com esta correção: typedValue.applyDimension( TypedValue.COMPLEX_UNIT_PX, valueInDp , getResources() .getDisplayMetrics());é de PX para DP, também esta é a melhor resposta
PaNaVTEC

1
@PaNaVTEC, a solução que você referenciou para dp para px está incorreta. applyDimensionproduz apenas pixels. Consulte android.googlesource.com/platform/frameworks/base/+/refs/heads/… em que nenhuma conversão é realizada COMPLEX_UNIT_PX.
Stephen Niedzielski

Esta resposta está completamente errada. Retorna valores finais em pixels, nunca em dp. Se você olhar o código-fonte, para o caso TypedValue.COMPLEX_UNIT_DIP, value * metrics.densityserá retornado, onde você realmente precisa value * metrics.density. Então, o que você está getting` valueInDp` não está em dppor valueInPxem px.
bitbybit

^ erro de digitação, eu quis dizer "... onde você realmente precisa value / metrics.density"
bitbybit

31

Maneira eficiente de sempre

DP para Pixel:

private int dpToPx(int dp)
{
    return (int) (dp * Resources.getSystem().getDisplayMetrics().density);
}

Pixel para DP:

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

Espero que isso ajude você.


4
Melhor porque não há necessidade de usar o Contexto :) Obrigado.
Ayaz Alifov 13/03/19

2
Melhor resposta! Infelizmente, a resposta "Marcado como correto" está errada. Especialmente porque usa o displayMetrics.xdpique é diferente em qualquer dispositivo. Infelizmente, essa resposta errada tem mais votos positivos.
161618

legais. deve ser marcado como melhor resposta. kotlin:private fun dpToPx(dp: Int) = (dp * Resources.getSystem().displayMetrics.density).toInt()
Raphael C

28

Basta ligar getResources().getDimensionPixelSize(R.dimen.your_dimension)para converter de dpunidades em pixels


3
De acordo com a documentação, é o mesmo que getDimension (), exceto que o valor retornado é convertido em pixels inteiros para uso como tamanho. Uma conversão de tamanho envolve arredondar o valor base e garantir que um valor base diferente de zero tenha pelo menos um pixel.
CJBS 28/07

14

Use esta função

private int dp2px(int dp) {
    return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, getResources().getDisplayMetrics());
}

6
px = dp * (dpi / 160)

dp = px * (160 / dpi)

embora, agora que penso nisso ... como haverá uma divisão por zero na fórmula original de Brams? O displayMetrics.densityDpi será 120, 160, 240 ou 320, nunca 0.
ct_rob

2
(displayMetrics.densityDpi / 160)- esta parte pode obter 0 em dispositivos pequenos, onde calcula, por exemplo 120/160, ambos os valores são int, o que resulta em 0. O que acaba como (int) (px/0).

ah, você está certo. Então, tudo o que ele precisa fazer é usar um longo em sua fórmula: 160.0
ct_rob 30/11

esta resposta é semelhante à superior porque o DisplayMetrics.DENSITY_DEFAULT tem 160 valor. mas, por favor, deixe-nos saber o que é o DPI aqui, como o calculamos.
John smith

3

Na maioria dos casos, as funções de conversão são chamadas com frequência. Podemos otimizá-lo adicionando memorização. Portanto, ele não calcula sempre que a função é chamada.

Vamos declarar um HashMap que irá armazenar os valores calculados.

private static Map<Float, Float> pxCache = new HashMap<>();

Uma função que calcula valores de pixel:

public static float calculateDpToPixel(float dp, Context context) {

        Resources resources = context.getResources();
        DisplayMetrics metrics = resources.getDisplayMetrics();
        float px = dp * (metrics.densityDpi / 160f);
        return px;

    }

Uma função de memorização que retorna o valor do HashMap e mantém o registro dos valores anteriores.

A memorização pode ser implementada de diferentes maneiras em Java. Para Java 7 :

public static float convertDpToPixel(float dp, final Context context) {

        Float f = pxCache.get(dp);
        if (f == null) {
            synchronized (pxCache) {
                f = calculateDpToPixel(dp, context);
                pxCache.put(dp, f);
            }

        }

        return f;
    }

O Java 8 suporta a função Lambda :

public static float convertDpToPixel(float dp, final Context context) {

        pxCache.computeIfAbsent(dp, y ->calculateDpToPixel(dp,context));
}

Obrigado.


boa adição às soluções fornecidas.
Bram

Eu ficaria surpreso se o cálculo da conversão não fosse pelo menos tão rápido quanto uma pesquisa no mapa, sem mencionar uma sincronizada. Você tem algum resultado de teste para demonstrar que essa otimização é benéfica? No Android, onde a memória de trabalho é limitada, a troca de memória pelo esforço de cálculo não é algo que você deve fazer sem um bom motivo.
Lorne Laliberte


2

As funções abaixo funcionaram bem para mim em todos os dispositivos.

É retirado de https://gist.github.com/laaptu/7867851

public static float convertPixelsToDp(float px){
    DisplayMetrics metrics = Resources.getSystem().getDisplayMetrics();
    float dp = px / (metrics.densityDpi / 160f);
    return Math.round(dp);
}

public static float convertDpToPixel(float dp){
    DisplayMetrics metrics = Resources.getSystem().getDisplayMetrics();
    float px = dp * (metrics.densityDpi / 160f);
    return Math.round(px);
}

1

Você pode usar [DisplayMatrics][1]e determinar a densidade da tela. Algo assim:

int pixelsValue = 5; // margin in pixels
float d = context.getResources().getDisplayMetrics().density;
int margin = (int)(pixelsValue * d);

Pelo que me lembro, é melhor usar pisos para compensações e arredondamentos para larguras.



1

// para obter em termos de decimal / flutuante

public static float convertPixelsToDp(float px, Context context) {

    Resources resources = context.getResources();
    DisplayMetrics metrics = resources.getDisplayMetrics();
    float dp = px / (metrics.densityDpi / 160f);
    return Math.round(dp);
}



public static float convertDpToPixel(float dp, Context context) {
    DisplayMetrics metrics = Resources.getSystem().getDisplayMetrics();
    float px = dp * (metrics.densityDpi / 160f);
    return Math.round(px);
}


// for  getting in terms of Integer

private int convertPxToDp(int px, Context context) {
    Resources resources = context.getResources();
    return Math.round(px / (resources.getDisplayMetrics().xdpi / DisplayMetrics.DENSITY_DEFAULT));
}


private int convertDpToPx(int dp, Context context) {
    Resources resources = context.getResources();

    return Math.round(dp * (resources.getDisplayMetrics().xdpi / DisplayMetrics.DENSITY_DEFAULT));

}

________________________________________________________________________________

public static float convertPixelsToDp(float px){
    DisplayMetrics metrics = Resources.getSystem().getDisplayMetrics();
    float dp = px / (metrics.densityDpi / 160f);
    return Math.round(dp);
}

public static float convertDpToPixel(float dp){
    DisplayMetrics metrics = Resources.getSystem().getDisplayMetrics();
    float px = dp * (metrics.densityDpi / 160f);
    return Math.round(px);
}


private int convertDpToPx(int dp){
    return Math.round(dp*(getResources().getDisplayMetrics().xdpi/DisplayMetrics.DENSITY_DEFAULT));

}

private int convertPxToDp(int px){
    return Math.round(px/(Resources.getSystem().getDisplayMetrics().xdpi/DisplayMetrics.DENSITY_DEFAULT));
}

1

Use estas extensões Kotlin:

/**
 * Converts Pixel to DP.
 */
val Int.pxToDp: Int
    get() = (this / Resources.getSystem().displayMetrics.density).toInt()

/**
 * Converts DP to Pixel.
 */
val Int.dpToPx: Int
    get() = (this * Resources.getSystem().displayMetrics.density).toInt()

0

Sinta-se livre para usar este método que escrevi:

int dpToPx(int dp)
{
    return (int) (dp * getResources().getDisplayMetrics().density + 0.5f);
}

0

A resposta aceita acima não é totalmente precisa. De acordo com as informações obtidas pela inspeção do código fonte do Android:

Resources.getDimension()e getDimensionPixelOffset()/ getDimensionPixelSize()diferem apenas no fato de o primeiro retornar, floatenquanto os dois últimos retornam o mesmo valor arredondado para o valor intapropriado. Para todos eles, o valor de retorno está em pixels brutos.

Todas as três funções são implementedy ligando Resources.getValue()e convertendo assim obtido TypedValueligando TypedValue.complexToDimension(), TypedValue.complexToDimensionPixelOffset()e TypedValue.complexToDimensionPixelSize(), respectivamente.

Portanto, se você deseja obter o valor "bruto" junto com a unidade especificada na fonte XML, chame Resources.getValue()e use os métodos da TypedValueclasse.


0

com ajuda de outras respostas, escrevi esta função.

public static int convertToPixels(Context context, int nDP)
{
        final float conversionScale = context.getResources().getDisplayMetrics().density; 
        return (int) ((nDP * conversionScale) + 0.5f) ;
}

0

Aqui está outra maneira de fazer isso usando extensões kotlin:

val Int.dpToPx: Int
    get() = Math.round(this * Resources.getSystem().displayMetrics.density)

val Int.pxToDp: Int
    get() = Math.round(this / Resources.getSystem().displayMetrics.density)

e então pode ser usado assim em qualquer lugar

12.dpToPx

244.pxToDp

0
DisplayMetrics displayMetrics = contaxt.getResources()
            .getDisplayMetrics();

    int densityDpi = (int) (displayMetrics.density * 160f);
    int ratio = (densityDpi / DisplayMetrics.DENSITY_DEFAULT);
    int px;
    if (ratio == 0) {
        px = dp;
    } else {
        px = Math.round(dp * ratio);

    }

0

a variação na resposta ct_robs acima, se você estiver usando números inteiros, que não apenas evita a divisão por 0, como também produz um resultado utilizável em dispositivos pequenos:

nos cálculos inteiros envolvendo divisão para maior precisão, multiplique primeiro antes de dividir para reduzir os efeitos de truncamento.

px = dp * dpi / 160
dp = px * 160 / dpi

5 * 120 = 600 / 160 = 3

ao invés de

5 * (120 / 160 = 0) = 0

se você quiser resultado arredondado, faça isso

px = (10 * dp * dpi / 160 + 5) / 10
dp = (10 * px * 160 / dpi + 5) / 10

10 * 5 * 120 = 6000 / 160 = 37 + 5 = 42 / 10 = 4
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.