Формула от px до dp, от dp до px android


150

Я пытаюсь вычислить переменное количество пикселей для плотности независимых пикселей и наоборот.

Эта формула (px to dp): dp = (int)(px / (displayMetrics.densityDpi / 160));не работает на небольших устройствах, потому что она делится на ноль.

Это моя формула dp to px:

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

Может ли кто-нибудь дать мне несколько советов?


1
Преобразование единиц измерения в пиксельные единицы developer.android.com/guide/practices/…
Падма Кумар

@ Брам: я думаю, что ваша формула просто отлично. Как вы получите деление на ноль? displayMetrics.densityDpi будет либо 120, 160, 240 или 320, но не 0.
ct_rob

Я согласен с @ct_rob. displayMetrics.densityDpi / 160 минимальное значение будет 0,75. Вы должны были приводить к int в неправильном месте.
TomTaila

Ответы:


318

Примечание: широко используемое решение выше основано на displayMetrics.density. Тем не менее, документы объясняют, что это значение является округленным значением, используемым с экраном «корзины». Например. на моем Nexus 10 он возвращает 2, где реальное значение будет 298dpi (реальное) / 160dpi (по умолчанию) = 1,8625.

В зависимости от ваших требований вам может потребоваться точное преобразование, которое может быть достигнуто следующим образом:

[Edit] Это не предназначено для смешивания с внутренним модулем dp Android, так как это, конечно, все еще основано на экранах. Используйте это там, где вы хотите, чтобы блок отображал один и тот же реальный размер на разных устройствах.

Преобразовать дп в пиксель:

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

Преобразовать пиксель в дп:

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

Обратите внимание, что есть свойства xdpi и ydpi, которые вы, возможно, захотите различить, но я не могу представить нормальное отображение, где эти значения сильно различаются.


8
Это не будет вычислять правильное значение для dp / px на многих устройствах (включая ваш Nexus 10)! Как вы говорите, displayMetrics.density округляется до ближайшего сегмента экрана, как и единица измерения dp! Попробуйте нарисовать один объект шириной 160 dp, а чуть ниже вы нарисуете другой объект шириной dpToPx (160) пикселей, и вы увидите, что размер этих двух объектов различен. Кроме того, некоторые телефоны (такие как Galaxy Mini и Galaxy S3 Mini) сообщают о совершенно неправильных значениях xdpi / ydpi, поэтому на этих телефонах ваши методы будут давать совершенно неверные результаты.
Нибариус

@nibarius Да, вы не можете смешать вычисления dp в штучной упаковке Android с вышеприведенным. Вышеупомянутое решение подразумевается как отдельная независимая от плотности величина, основанная на точной физике устройства. Требуется, например, где вы хотите показать линию одинаковой реальной длины на разных устройствах. Конечно, если некоторые устройства неправильно настроили входы xdpi / ydpi, он не будет работать там.
Бачи

1
xdpi и ydpi не должны использоваться, потому что они неточны на многих устройствах, иногда на многих. Только DisplayMetrics.densityDpi является надежным, что вызывает сожаление, так как это неточно по дизайну. Дополнительную информацию смотрите в ветке форума Google: groups.google.com/forum/#!topic/android-developers/g56jV0Hora0
Марк Макклелланд,

1
Я нашел этот ответ и фактически использовал много похожих решений, но я просто просмотрел документы и обнаружил, что getDimensionPixelOffSet с заданным измерением (объявленным в dip / dp) возвращает смещение в пикселях точно так же, как код ручной работы. Я тестировал и работал без нареканий. Надеюсь, поможет!
Уильям Гувея

1
Меххх, это не дает правильного значения. Попробуйте: Resources r = getResources (); float px = TypedValue.applyDimension (TypedValue.COMPLEX_UNIT_DIP, 14, r.getDisplayMetrics ()); Как описано здесь: stackoverflow.com/questions/4605527/converting-pixels-to-dp
Рик ван

103

Я решил свою проблему, используя следующие формулы. Пусть другие люди выиграют от этого.

dp to px:

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

px до dp:

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

27
@Vame Добавление 0,5 используется для округления ВВЕРХ до ближайшего целочисленного значения. Добавляется 0,5, а затем результат вычисления приводится к целому числу, в результате чего он усекает мантиссу, а характеристика остается в виде правильно округленного целочисленного значения.
Келли Копли

3
Это не всегда правильно. Когда у меня есть два макета, один внутри другого, а затем я скругляю углы каждого вида (один с использованием dp, другой - с преобразованием в dp), углы не совпадают!
Марек

У меня не было проблем с этой техникой. Я использовал это для разных макетов, и это всегда работало, как ожидалось. Конечно, я использовал это несколько лет назад, и я не уверен, что это все еще работает. Возможно, вы могли бы попробовать другой метод в ответе PanaVTEC. Также может быть, что углы закругления - это нечто большее, чем просто вычисления dp / px.
Брэм

5
Это то же решение, которое принято на сайте разработчиков Android, так что я думаю, что оно правильное :). http://developer.android.com/guide/practices/screens_support.html#dips-pels
alocaly

1
+1 Спасибо, мужик. Работал как шарм! Спасибо, что поделились этим решением с нами.
Симон Дорочак

34

px до dp:

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

7
Это от DP до PX, но с этим исправлением: typedValue.applyDimension( TypedValue.COMPLEX_UNIT_PX, valueInDp , getResources() .getDisplayMetrics());от PX до DP, также это лучший ответ
PaNaVTEC

1
@PaNaVTEC, решение, которое вы указали для dp to px, неверно. applyDimensionтолько дает пикселей. См. Android.googlesource.com/platform/frameworks/base/+/refs/heads/…, где преобразование не выполняется COMPLEX_UNIT_PX.
Стивен Недзельски

Этот ответ совершенно неверный. Возвращает конечные значения в пикселях, а не в dp. Если вы посмотрите на исходный код, для случая TypedValue.COMPLEX_UNIT_DIP, value * metrics.densityвозвращается, где вам действительно нужно value * metrics.density. Так что вы getting` valueInDp` НЕ в dpтечение valueInPxв px.
bitbybit

^ опечатка, я имел в виду "... где вам на самом деле нужно value / metrics.density"
bitbybit

31

Эффективный способ когда-либо

DP to Pixel:

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

Пиксель в DP:

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

Надеюсь, что это поможет вам.


4
Лучше, потому что нет необходимости использовать контекст :) Спасибо.
Аяз

2
Лучший ответ! К сожалению, ответ «помечен как правильный» неверен. Тем более что он использует displayMetrics.xdpiкоторый отличается на любом устройстве. К сожалению, этот неправильный ответ также имеет больше голосов.
Тоом

отлично. должен быть помечен как лучший ответ. Котлин:private fun dpToPx(dp: Int) = (dp * Resources.getSystem().displayMetrics.density).toInt()
Рафаэль C

28

Просто позвоните, getResources().getDimensionPixelSize(R.dimen.your_dimension)чтобы преобразовать из dpединиц в пиксели


3
Согласно документации, это то же самое, что и getDimension (), за исключением того, что возвращаемое значение преобразуется в целочисленные пиксели для использования в качестве размера. Преобразование размера включает в себя округление базового значения и обеспечение того, чтобы ненулевое базовое значение имело размер не менее одного пикселя.
CJBS


6
px = dp * (dpi / 160)

dp = px * (160 / dpi)

хотя, теперь, когда я думаю об этом ... как будет когда-либо деление на ноль в оригинальной формуле Брамса? displayMetrics.densityDpi будет 120, 160, 240 или 320, но не 0.
ct_rob

2
(displayMetrics.densityDpi / 160)- эта часть может получить 0 на небольших устройствах, там она вычисляет, например 120/160, оба значения являются целыми числами, что приводит к 0. Что заканчивается как (int) (px/0).

ах, ты прав. Поэтому все, что ему нужно сделать, это использовать длинную в своей формуле: 160.0
ct_rob

этот ответ похож на верхний, поскольку DisplayMetrics.DENSITY_DEFAULT имеет значение 160. но не могли бы вы сообщить нам, что такое DPI, как мы его рассчитываем?
Джон Смит

3

В большинстве случаев функции преобразования вызываются часто. Мы можем оптимизировать его, добавив памятку. Таким образом, он не вычисляет каждый раз, когда вызывается функция.

Давайте объявим HashMap, в котором будут храниться вычисленные значения.

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

Функция, которая вычисляет значения пикселей:

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

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

    }

Функция запоминания, которая возвращает значение из HashMap и поддерживает запись предыдущих значений.

Мемоизация может быть реализована по-разному в Java. Для 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 поддерживает функцию Lambda :

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

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

Спасибо.


приятное дополнение к данным решениям.
Брэм

Я был бы удивлен, если бы расчет конверсии не был так же быстр, как поиск по карте, не говоря уже о синхронизированном. Есть ли у вас какие-либо результаты испытаний, чтобы продемонстрировать, что эта оптимизация полезна? На Android, где рабочая память ограничена, обменять память на вычисления не стоит без веской причины.
Лорн Лалиберте


2

Ниже функции хорошо работали для меня на всех устройствах.

Это взято от 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

Вы можете использовать [DisplayMatrics][1]и определить плотность экрана. Что-то вроде этого:

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

Насколько я помню, лучше использовать настил для смещений и округление для ширины.



1

// для получения в виде десятичного числа / числа с плавающей запятой

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

Используйте эти расширения 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

Не стесняйтесь использовать этот метод, который я написал:

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

0

Ответ, принятый выше, не является полностью точным. Согласно информации, полученной при проверке исходного кода Android:

Resources.getDimension()и getDimensionPixelOffset()/ getDimensionPixelSize()отличаются только тем, что первые возвращаются, в floatто время как последние два возвращают одинаковое значение, округленное до intподходящего. Для всех них возвращаемое значение в необработанных пикселях.

Все три функции implementedy путем вызова Resources.getValue()и преобразования полученного таким образом TypedValue, вызвав TypedValue.complexToDimension(), TypedValue.complexToDimensionPixelOffset()и TypedValue.complexToDimensionPixelSize(), соответственно.

Поэтому, если вы хотите получить «необработанное» значение вместе с единицей, указанной в источнике XML, вызовите Resources.getValue()и используйте методы TypedValueкласса.


0

с помощью других ответов я написал эту функцию.

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

0

Вот другой способ сделать это с помощью расширений 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)

и тогда его можно использовать как угодно из любого места

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

Вариант ct_robs ответ выше, если вы используете целые числа, это не только позволяет избежать деления на 0, но также дает полезный результат на небольших устройствах:

в целочисленных вычислениях, включающих деление для наибольшей точности, сначала умножьте перед делением, чтобы уменьшить эффекты усечения.

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

5 * 120 = 600 / 160 = 3

вместо того

5 * (120 / 160 = 0) = 0

если вы хотите округленный результат, сделайте это

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

10 * 5 * 120 = 6000 / 160 = 37 + 5 = 42 / 10 = 4
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.