Formuła px do dp, dp do px android


150

Próbuję obliczyć zmienną liczbę pikseli do niezależnych od gęstości pikseli i odwrotnie.

Ta formuła (px to dp): dp = (int)(px / (displayMetrics.densityDpi / 160));nie działa na małych urządzeniach, ponieważ jest podzielona przez zero.

To jest moja formuła dp do px:

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

Czy ktoś mógłby mi udzielić wskazówek?


1
Konwersja jednostek dp na jednostki pikseli developer.android.com/guide/practices/ ...
Padma Kumar

@Bram: Myślę, że twoja formuła jest w porządku. Jak uzyskasz dzielenie przez zero? displayMetrics.densityDpi będzie wynosić 120, 160, 240 lub 320, nigdy 0.
ct_rob

Zgadzam się z @ct_rob. Wartość minimalna displayMetrics.densityDpi / 160 będzie wynosić 0,75. Musiałeś castować na int w niewłaściwym miejscu.
TomTaila

Odpowiedzi:


318

Uwaga: szeroko stosowane rozwiązanie powyżej opiera się na displayMetrics.density. Jednak w dokumentacji wyjaśniono, że ta wartość jest wartością zaokrągloną, używaną z „zasobnikami” ekranu. Na przykład. na moim Nexusie 10 zwraca 2, gdzie rzeczywista wartość wynosiłaby 298 dpi (rzeczywista) / 160 dpi (domyślnie) = 1,8625.

W zależności od wymagań możesz potrzebować dokładnej transformacji, którą można osiągnąć w następujący sposób:

[Edytuj] To nie jest przeznaczone do mieszania z wewnętrzną jednostką dp Androida, ponieważ jest to oczywiście nadal oparte na zasobnikach ekranu. Użyj tego, jeśli chcesz, aby jednostka powinna renderować ten sam rzeczywisty rozmiar na różnych urządzeniach.

Konwertuj dp na piksel:

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

Konwertuj piksel na dp:

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

Zauważ, że istnieją właściwości xdpi i ydpi, możesz chcieć je rozróżnić, ale nie wyobrażam sobie rozsądnego wyświetlacza, w którym te wartości znacznie się różnią.


8
To nie obliczy prawidłowej wartości dp / px na wielu urządzeniach (w tym na Twoim Nexusie 10)! Jak mówisz, displayMetrics.density jest zaokrąglane do najbliższego segmentu ekranu, ale tak samo jest z jednostką dp! Spróbuj narysować jeden obiekt o szerokości 160 dp, a tuż pod nim narysuj inny obiekt o szerokości dpToPx (160) pikseli, a zobaczysz, że rozmiary obu obiektów są różne. Również niektóre telefony (takie jak Galaxy Mini i Galaxy S3 Mini) zgłaszają całkowicie błędne wartości xdpi / ydpi, więc w tych telefonach metody zwracają całkowicie błędne wyniki.
nibarius

@nibarius Tak, nie można łączyć pudełkowych obliczeń DP systemu Android z powyższymi. Powyższe rozwiązanie jest rozumiane jako oddzielna wartość niezależna od gęstości, oparta na dokładnej fizyce urządzenia. Potrzebne np. Gdy chcesz pokazać linię o tej samej rzeczywistej długości na różnych urządzeniach. Oczywiście, jeśli wejścia xdpi / ydpi nie są poprawnie ustawione przez niektóre urządzenia, nie będzie tam działać.
Bachi

1
xdpi i ydpi nie powinny być używane, ponieważ są one niedokładne na wielu urządzeniach, czasami bardzo. Tylko DisplayMetrics.densityDpi jest niezawodny, co jest niefortunne, ponieważ jest nieprecyzyjny z założenia. Zobacz wątek na forum Google, aby uzyskać więcej informacji: groups.google.com/forum/#!topic/android-developers/g56jV0Hora0
Mark McClelland

1
Znalazłem tę odpowiedź i faktycznie użyłem wielu podobnych rozwiązań, ale właśnie przejrzałem dokumentację i znalazłem getDimensionPixelOffSet z podanym wymiarem (zadeklarowanym w dip / dp) zwraca przesunięcie w pikselach, tak jak ręcznie robiony kod. Przetestowałem i działałem bez zarzutu. Mam nadzieję, że to pomoże!
william gouvea

1
Mehhh, to nie daje prawidłowej wartości. Spróbuj: Zasoby r = getResources (); float px = TypedValue.applyDimension (TypedValue.COMPLEX_UNIT_DIP, 14, r.getDisplayMetrics ()); Jak opisano tutaj: stackoverflow.com/questions/4605527/converting-pixels-to-dp
Rik van Velzen

103

Rozwiązałem swój problem, używając następujących wzorów. Niech skorzystają na tym inni ludzie.

dp do px:

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

px do dp:

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

27
@Vame Dodanie 0,5 służy do zaokrągleń w GÓRĘ do najbliższej liczby całkowitej. 0,5 jest dodawane, a następnie wynik obliczenia jest rzutowany jako int, powodując obcięcie mantysy i pozostawienie charakterystyki jako odpowiednio zaokrąglonej wartości całkowitej.
Kelly Copley

3
Nie zawsze jest to poprawne. Kiedy mam dwa układy jeden w drugim, a następnie zaokrąglam rogi każdego widoku (jeden za pomocą dp, drugi konwertując na dp) rogi nie pasują!
Marek

Nie miałem żadnych problemów z tą techniką. Używałem tego do różnych układów i zawsze działało zgodnie z oczekiwaniami. Oczywiście korzystałem z tego kilka lat temu i nie jestem pewien, czy nadal działa. Być może mógłbyś wypróbować inną technikę w odpowiedzi PanaVTEC. Może się również zdarzyć, że zaokrąglanie rogów to coś więcej niż tylko obliczenia dp / px.
Bram,

5
To to samo rozwiązanie, które przyjęto na stronie programistów Androida, więc myślę, że jest poprawne :). http://developer.android.com/guide/practices/screens_support.html#dips-pels
alocaly

1
+1 Dzięki stary. Działał jak urok! Dziękujemy za udostępnienie nam tego rozwiązania.
Simon Dorociak

34

px do dp:

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

7
To jest z DP na PX, ale z tą poprawką: typedValue.applyDimension( TypedValue.COMPLEX_UNIT_PX, valueInDp , getResources() .getDisplayMetrics());jest z PX na DP, również to jest najlepsza odpowiedź
PaNaVTEC

1
@PaNaVTEC, rozwiązanie, do którego odwołałeś się dla dp do px, jest nieprawidłowe. applyDimensiondaje tylko piksele. Zobacz android.googlesource.com/platform/frameworks/base/+/refs/heads/…, gdzie nie jest wykonywana żadna konwersja COMPLEX_UNIT_PX.
Stephen Niedzielski

Ta odpowiedź jest po prostu błędna. Zwraca końcowe wartości w pikselach, nigdy w dp. Jeśli spojrzeć na kod źródłowy, na razie TypedValue.COMPLEX_UNIT_DIP, value * metrics.densityjest zwracana, gdzie rzeczywiście trzeba value * metrics.density. Więc czego getting` valueInDp` nie jest dpdla valueInPxw px.
bitbybit,

^ literówka, miałem na myśli „... gdzie naprawdę potrzebujesz value / metrics.density
bitbybit,

31

Wydajny sposób zawsze

DP na piksel:

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

Pixel do DP:

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

Mam nadzieję, że to ci pomoże.


4
Lepsze, ponieważ nie ma potrzeby używania kontekstu :) Dzięki.
Ayaz Alifov

2
Najlepsza odpowiedź! Niestety odpowiedź „Oznaczono jako poprawna” jest błędna. Zwłaszcza, że ​​używa tego, displayMetrics.xdpico jest inne na każdym urządzeniu. Niestety, ta zła odpowiedź ma również najwięcej głosów pozytywnych.
jutro

miły. należy oznaczyć jako najlepszą odpowiedź. kotlin:private fun dpToPx(dp: Int) = (dp * Resources.getSystem().displayMetrics.density).toInt()
Raphael C

28

Po prostu zadzwoń, getResources().getDimensionPixelSize(R.dimen.your_dimension)aby przekonwertować z dpjednostek na piksele


3
Zgodnie z dokumentacją jest to to samo co getDimension (), z wyjątkiem tego, że zwracana wartość jest konwertowana na piksele całkowite w celu użycia jako rozmiaru. Konwersja rozmiaru obejmuje zaokrąglenie wartości bazowej i upewnienie się, że niezerowa wartość podstawowa ma rozmiar co najmniej jednego piksela.
CJBS

14

Użyj tej funkcji

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)

chociaż teraz, kiedy o tym myślę ... jak będzie kiedykolwiek istnieć dzielenie przez zero w oryginalnej formule Bramsa? displayMetrics.densityDpi będzie wynosić 120, 160, 240 lub 320, nigdy 0.
ct_rob

2
(displayMetrics.densityDpi / 160)- ta część może otrzymać 0 na małych urządzeniach, tam na przykład oblicza 120/160, obie wartości są int, co daje 0. Co kończy się jako (int) (px/0).

ah, masz rację. Więc wszystko, co musi zrobić, to użyć długości w swoim wzorze: 160.0
ct_rob

ta odpowiedź jest podobna do górnej, ponieważ DisplayMetrics.DENSITY_DEFAULT ma wartość 160. ale czy możesz nam powiedzieć, co to jest DPI tutaj, jak to obliczamy.
John Smith

3

W większości przypadków funkcje konwersji są wywoływane często. Możemy to zoptymalizować dodając zapamiętywanie. Dlatego nie wykonuje obliczeń za każdym razem, gdy wywoływana jest funkcja.

Zadeklarujmy HashMap, który będzie przechowywać obliczone wartości.

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

Funkcja obliczająca wartości pikseli:

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

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

    }

Funkcja zapamiętywania, która zwraca wartość z HashMap i przechowuje zapis poprzednich wartości.

Memoizacja może być implementowana na różne sposoby w Javie. Dla 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 obsługuje funkcję Lambda :

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

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

Dzięki.


fajny dodatek do podanych rozwiązań.
Bram

Byłbym zaskoczony, gdyby obliczenia konwersji nie były co najmniej tak szybkie jak wyszukiwanie mapy, nie wspominając o zsynchronizowanym. Czy masz jakieś wyniki testów, które pokazują, że ta optymalizacja jest korzystna? W systemie Android, w którym pamięć robocza jest ograniczona, wymiana pamięci na wysiłek obliczeniowy nie jest czymś, co powinieneś robić bez dobrego powodu.
Lorne Laliberte


2

Poniższe funkcje działały dobrze dla mnie na różnych urządzeniach.

Pochodzi z 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

Możesz użyć [DisplayMatrics][1]i określić gęstość ekranu. Coś takiego:

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

O ile dobrze pamiętam, do odsadzeń i zaokrąglania na szerokościach lepiej jest używać podłóg.



1

// do uzyskania wartości Decimal / Float

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

Użyj tych rozszerzeń 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

Zapraszam do skorzystania z tej metody, którą napisałem:

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

0

Przyjęta powyżej odpowiedź nie jest w pełni poprawna. Według informacji uzyskanych podczas inspekcji kodu źródłowego Androida:

Resources.getDimension()i getDimensionPixelOffset()/ getDimensionPixelSize()różnią się tylko tym, że pierwszy zwraca, floatpodczas gdy dwa ostatnie zwracają tę samą wartość intodpowiednio zaokrągloną . Dla wszystkich zwracana jest wartość w surowych pikselach.

Wszystkie trzy funkcje są implementowane przez wywołanie Resources.getValue()i konwersję uzyskaną w ten sposób TypedValueprzez wywołanie , odpowiednio TypedValue.complexToDimension(), TypedValue.complexToDimensionPixelOffset()i TypedValue.complexToDimensionPixelSize().

Dlatego jeśli chcesz uzyskać „surową” wartość razem z jednostką określoną w źródle XML, wywołaj Resources.getValue()i użyj metod TypedValueklasy.


0

z pomocą innych odpowiedzi napisałem tę funkcję.

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

0

Oto inny sposób na zrobienie tego za pomocą rozszerzeń 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)

a następnie może być używany w ten sposób z dowolnego miejsca

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

odmiana odpowiedzi ct_robs powyżej, jeśli używasz liczb całkowitych, które nie tylko pozwalają uniknąć dzielenia przez 0, ale także dają użyteczny wynik na małych urządzeniach:

w obliczeniach całkowitych obejmujących dzielenie w celu uzyskania największej dokładności należy najpierw pomnożyć przed podzieleniem, aby zredukować efekty obcięcia.

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

5 * 120 = 600 / 160 = 3

zamiast

5 * (120 / 160 = 0) = 0

jeśli chcesz zaokrąglony wynik, zrób to

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

10 * 5 * 120 = 6000 / 160 = 37 + 5 = 42 / 10 = 4
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.