Fórmula px a dp, dp a px android


150

Estoy tratando de calcular una cantidad variable de píxeles en píxeles independientes de la densidad y viceversa.

Esta fórmula (px to dp): dp = (int)(px / (displayMetrics.densityDpi / 160));no funciona en dispositivos pequeños porque está dividida por cero.

Esta es mi fórmula de dp a px:

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

¿Alguien podría darme algunos consejos?


1
Conversión de unidades dp a unidades de píxeles developer.android.com/guide/practices/…
Padma Kumar

@Bram: Creo que tu fórmula está bien. ¿Cómo obtendrás una división por cero? displayMetrics.densityDpi será 120, 160, 240 o 320, nunca 0.
ct_rob

Estoy de acuerdo con @ct_rob. El valor mínimo de displayMetrics.densityDpi / 160 será 0.75. Debes haber enviado a int en el lugar incorrecto.
TomTaila

Respuestas:


318

Nota: La solución ampliamente utilizada anterior se basa en displayMetrics.density. Sin embargo, los documentos explican que este valor es un valor redondeado, utilizado con los 'cubos' de la pantalla. P.ej. en mi Nexus 10 devuelve 2, donde el valor real sería 298 ppp (real) / 160 ppp (predeterminado) = 1.8625.

Dependiendo de sus requisitos, es posible que necesite la transformación exacta, que se puede lograr de esta manera:

[Editar] Esto no está destinado a ser mezclado con la unidad dp interna de Android, ya que esto, por supuesto, todavía se basa en los cubos de la pantalla. Use esto donde desee una unidad que debería representar el mismo tamaño real en diferentes dispositivos.

Convierta dp a píxel:

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

Convierte píxeles a dp:

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

Tenga en cuenta que hay propiedades xdpi e ydpi, es posible que desee distinguir, pero no puedo imaginar una visualización sensata en la que estos valores difieran mucho.


8
¡Esto no calculará el valor correcto para dp / px en muchos dispositivos (incluido su Nexus 10)! Como dices, displayMetrics.density se redondea al segmento de pantalla más cercano, ¡pero también lo es la unidad dp! Intente dibujar un objeto que tenga 160dp de ancho y justo debajo de él dibuje otro objeto que tenga dpToPx (160) píxeles de ancho y verá que el tamaño de los dos objetos es diferente. Además, algunos teléfonos (como Galaxy Mini y Galaxy S3 Mini) informan valores completamente incorrectos para xdpi / ydpi, por lo que en estos teléfonos sus métodos arrojarán resultados completamente incorrectos.
nibarius

@nibarius Sí, no puedes mezclar los cálculos dp en caja de Android con los anteriores. La solución anterior se entiende como un valor independiente de densidad independiente basado en la física exacta del dispositivo. Necesario, por ejemplo, donde desea mostrar una línea de la misma longitud real en diferentes dispositivos. Por supuesto, si las entradas xdpi / ydpi no están configuradas correctamente por algunos dispositivos, no funcionará allí.
Bachi

1
xdpi e ydpi no deben usarse, ya que son inexactos en muchos dispositivos, a veces por mucho. Solo DisplayMetrics.densityDpi es confiable, lo cual es lamentable, ya que es impreciso por diseño. Consulte el hilo del foro de Google para obtener más información: groups.google.com/forum/#!topic/android-developers/g56jV0Hora0
Mark McClelland

1
Encontré esta respuesta y en realidad usé muchas soluciones similares, pero solo revisé los documentos y encontré que getDimensionPixelOffSet con una dimensión dada (declarada en dip / dp) devuelve el desplazamiento en píxeles al igual que el código hecho a mano. Probé y trabajé sin problemas. ¡Espero eso ayude!
william gouvea

1
Mehhh, esto no da el valor correcto. Prueba: Recursos r = getResources (); float px = TypedValue.applyDimension (TypedValue.COMPLEX_UNIT_DIP, 14, r.getDisplayMetrics ()); Como se describe aquí: stackoverflow.com/questions/4605527/converting-pixels-to-dp
Rik van Velzen

103

Resolví mi problema usando las siguientes fórmulas. Que otras personas se beneficien de ello.

dp a px:

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

px a dp:

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

27
@Vame La suma de 0.5 se usa para redondear hacia ARRIBA al valor entero más cercano. Se agrega 0.5 y luego el resultado del cálculo se convierte en un int haciendo que trunca la mantisa y dejando la característica como un valor entero correctamente redondeado.
Kelly Copley

3
Esto no siempre es correcto. Cuando tengo dos diseños uno dentro de otro y luego redondeo las esquinas de cada vista (una usando dp, otra convirtiendo a dp) ¡las esquinas no coinciden!
Marek

No tuve ningún problema con esta técnica. Utilicé esto para diferentes diseños y siempre funcionó como se esperaba. Por supuesto, lo usé hace unos años y no estoy seguro de si todavía funciona. Quizás podría probar la otra técnica en la respuesta de PanaVTEC. También podría ser que hay más para redondear esquinas que solo cálculos dp / px.
Bram

55
Es la misma solución que se adopta en el sitio para desarrolladores de Android, así que supongo que es correcta :). http://developer.android.com/guide/practices/screens_support.html#dips-pels
alocaly

1
+1 Gracias hombre. ¡Trabajado como un encanto! Gracias por compartir esta solución con nosotros.
Simon Dorociak

34

px a dp:

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

77
Esto es de DP a PX, pero con esta corrección: typedValue.applyDimension( TypedValue.COMPLEX_UNIT_PX, valueInDp , getResources() .getDisplayMetrics());es de PX a DP, también esta es la mejor respuesta
PaNaVTEC

1
@PaNaVTEC, la solución a la que hizo referencia para dp a px es incorrecta. applyDimensionsolo produce píxeles. Consulte android.googlesource.com/platform/frameworks/base/+/refs/heads/… en el que no se realiza ninguna conversión COMPLEX_UNIT_PX.
Stephen Niedzielski

Esta respuesta es completamente incorrecta. Devuelve los valores finales en píxeles, nunca en dp. Si observa el código fuente, para el caso TypedValue.COMPLEX_UNIT_DIP, value * metrics.densityse devuelve, donde realmente lo necesita value * metrics.density. Entonces, lo que está obteniendo` valueInDp` NO está dppara valueInPxadentro px.
bitbybit

^ error tipográfico, quise decir "... donde realmente necesitas value / metrics.density"
bitbybit

31

De manera eficiente siempre

DP a píxel:

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

Pixel a DP:

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

Espero que esto te ayudará.


44
Mejor porque no hay necesidad de usar Context :) Gracias.
Ayaz Alifov

2
¡La mejor respuesta! Lamentablemente, la respuesta "Marcado como correcto" es incorrecta. Especialmente porque usa lo displayMetrics.xdpique es diferente en cualquier dispositivo. Lamentablemente, esta respuesta incorrecta también tiene más votos a favor.
Toom

bonito. debe marcarse como la mejor respuesta. Kotlin:private fun dpToPx(dp: Int) = (dp * Resources.getSystem().displayMetrics.density).toInt()
Raphael C

28

Simplemente llame getResources().getDimensionPixelSize(R.dimen.your_dimension)para convertir de dpunidades a píxeles


3
Según la documentación, esto es lo mismo que getDimension (), excepto que el valor devuelto se convierte a píxeles enteros para su uso como tamaño. Una conversión de tamaño implica redondear el valor base y garantizar que un valor base distinto de cero tenga al menos un píxel de tamaño.
CJBS

14

Use esta función

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)

aunque, ahora que lo pienso ... ¿cómo habrá una división por cero en la fórmula original de Brams? displayMetrics.densityDpi será 120, 160, 240 o 320, nunca 0.
ct_rob

2
(displayMetrics.densityDpi / 160)- esta parte puede obtener 0 en dispositivos pequeños, allí calcula, por ejemplo 120/160, ambos valores son int, lo que resulta en 0. Lo que termina como (int) (px/0).

Ah, tienes razón. Entonces, todo lo que necesita hacer es usar un largo en su fórmula:
160.0

esta respuesta es similar a la superior debido a DisplayMetrics.DENSITY_DEFAULT tiene un valor de 160. pero ¿puede decirnos qué es DPI aquí cómo lo calculamos?
John smith

3

En la mayoría de los casos, las funciones de conversión se llaman con frecuencia. Podemos optimizarlo agregando memorización. Por lo tanto, no calcula cada vez que se llama a la función.

Declaremos un HashMap que almacenará los valores calculados.

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

Una función que calcula valores de píxeles:

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

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

    }

Una función de memorización que devuelve el valor de HashMap y mantiene el registro de los valores anteriores.

La memorización se puede implementar de diferentes maneras en 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;
    }

Java 8 admite la función Lambda :

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

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

Gracias.


Buena adición a las soluciones dadas.
Bram

Me sorprendería si el cálculo de conversión no fuera al menos tan rápido como una búsqueda en el mapa, sin mencionar uno sincronizado. ¿Tiene algún resultado de prueba para demostrar que esta optimización es beneficiosa? En Android, donde la memoria de trabajo es limitada, cambiar la memoria por esfuerzo de cálculo no es algo que deba hacer sin una buena razón.
Lorne Laliberte


2

Las siguientes funciones funcionaron bien para mí en todos los dispositivos.

Está tomado 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

Puede usar [DisplayMatrics][1]y determinar la densidad de la pantalla. Algo como esto:

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

Como recuerdo, es mejor usar pisos para desplazamientos y redondeos para anchos.



1

// para obtener en términos de decimal / flotante

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 extensiones de 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

Siéntase libre de usar este método que escribí:

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

0

La respuesta aceptada anteriormente no es totalmente precisa. Según la información obtenida al inspeccionar el código fuente de Android:

Resources.getDimension()y getDimensionPixelOffset()/ getDimensionPixelSize()difiero solo en que el primero devuelve floatmientras que los dos últimos devuelven el mismo valor redondeado a intapropiadamente. Para todos ellos, el valor de retorno está en píxeles sin formato.

Las tres funciones son implementedy llamando Resources.getValue()y convertir así obtenida TypedValuellamando TypedValue.complexToDimension(), TypedValue.complexToDimensionPixelOffset()y TypedValue.complexToDimensionPixelSize(), respectivamente.

Por lo tanto, si desea obtener un valor "en bruto" junto con la unidad especificada en la fuente XML, llame Resources.getValue()y use los métodos de la TypedValueclase.


0

Con la ayuda de otras respuestas escribí esta función.

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

0

Aquí hay otra forma de hacerlo usando las extensiones de 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)

y luego se puede usar así desde cualquier 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

La variación de la respuesta ct_robs anterior, si está utilizando números enteros, que no solo evita dividir por 0, también produce un resultado utilizable en dispositivos pequeños:

en cálculos enteros que involucran división para mayor precisión, multiplique primero antes de dividir para reducir los efectos de truncamiento.

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

5 * 120 = 600 / 160 = 3

en vez de

5 * (120 / 160 = 0) = 0

si quieres un resultado redondeado haz esto

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

10 * 5 * 120 = 6000 / 160 = 37 + 5 = 42 / 10 = 4
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.