Android中像素(px)和密度无关像素(dp)的转换公式

161

我正在尝试计算可变数量的像素到密度无关像素和反之。

这个公式 (px转dp): dp = (int)(px / (displayMetrics.densityDpi / 160)); 在小设备上不起作用,因为它会被零除。

这是我的dp转px公式:

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

有人能给我一些指针吗?


1
将dp单位转换为像素单位。http://developer.android.com/guide/practices/screens_support.html#dips-pels - Padma Kumar
@Bram:我认为你的公式没问题。你怎么会出现除以零的情况呢?displayMetrics.densityDpi 的值只会是 120、160、240 或 320,绝不会是 0。 - ct_rob
1
我同意 @ct_rob 的观点。displayMetrics.densityDpi / 160 的最小值将为0.75。你可能在错误的地方进行了int类型转换。 - TomTaila
21个回答

331

注意:上面广泛使用的解决方案基于 displayMetrics.density。然而,文档解释说这个值是一个四舍五入的值,与屏幕“桶”一起使用。例如,在我的Nexus 10上,它返回2,而实际值将是298dpi(真实值)/ 160dpi(默认值)= 1.8625。

根据您的要求,您可能需要精确的转换,可以通过以下方式实现:

[编辑] 这不是Android内部dp单位的混合使用,因为这当然仍然基于屏幕桶。在您希望在不同设备上呈现相同实际大小的单位时,请使用此选项。

将dp转换为像素:

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

将像素转换为 dp:

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

请注意xdpi和ydpi属性,您可能需要进行区分,但我无法想象一个理智的显示器,在这些值有很大差异的情况下。


13
这段代码在许多设备(包括您的Nexus 10)上无法正确计算dp / px的正确值!正如您所说,displayMetrics.density会四舍五入到最近的屏幕桶,但是dp单位也是如此!尝试绘制一个宽度为160dp的对象,然后在其下方绘制另一个宽度为dpToPx(160)像素的对象,您会发现两个对象的大小不同。而且,某些手机(例如Galaxy Mini和Galaxy S3 Mini)报告xdpi / ydpi的完全错误值,因此在这些手机上,您的方法将返回完全错误的结果。 - nibarius
@nibarius,是的,您不能将Android的封装dp计算与上述内容混合使用。上述解决方案旨在基于精确设备物理特性提供单独的密度无关值。例如,在您想要在不同设备上显示完全相同实际长度的线时需要使用此方法。当然,如果某些设备未正确设置xdpi / ydpi输入,则无法正常工作。 - Bachi
1
不应使用xdpi和ydpi,因为它们在许多设备上都不准确,有时相差很大。只有DisplayMetrics.densityDpi是可靠的,这是不幸的,因为它是按设计不精确的。请参阅Google论坛主题以获取更多信息:https://groups.google.com/forum/#!topic/android-developers/g56jV0Hora0 - Mark McClelland
1
我找到了这个回答并且实际上使用了很多类似的解决方案,但是我刚刚查阅了文档并发现通过给定在dip/dp中声明的一个尺寸,getDimensionPixelOffSet会返回像手写的代码一样的像素偏移量。我进行了测试并且它完美地工作。希望这有所帮助! - william gouvea
1
这个不行,它没有给出正确的值。试试这个: Resources r = getResources(); float px = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 14, r.getDisplayMetrics());如此描述:https://dev59.com/MW445IYBdhLWcg3w_O8m - Rik van Velzen
显示剩余5条评论

103

我通过使用以下公式解决了我的问题。希望其他人也能从中受益。

dp 转 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);

28
在四舍五入至最接近的整数时,通常会加上0.5。先加上0.5,然后将计算结果转换为整数,这会截断尾数并使特征成为正确舍入的整数值。 - Kelly Copley
3
这并不总是正确的。当我有两个布局,一个嵌套在另一个里面,然后我对每个视图的角进行圆角处理时(一个使用dp,另一个转换为dp),它们的角就不匹配了! - Marek
我在使用这种技术时没有遇到任何问题。我用它来处理不同的布局,它总是按预期工作。当然,我是几年前使用的,我不确定它是否仍然有效。也许你可以尝试一下 PanaVTEC 回答中提到的另一种技术。可能圆角处理还需要更多的 dp/px 计算以外的东西。 - Bram
5
采用的是 Android 开发者网站上采用的相同解决方案,因此我猜想它是正确的 :) 。http://developer.android.com/guide/practices/screens_support.html#dips-pels - alocaly
1
+1 谢谢,太妙了!感谢您与我们分享这个解决方案。 - Simon Dorociak
显示剩余2条评论

43

史上最高效的方法

从 DP 到像素:

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

希望这能对你有所帮助。


6
更好,因为不需要使用上下文 :) 谢谢。 - Ayaz Alifov
2
最佳答案!不幸的是,“标记为正确”的答案是错误的。特别是它使用的 displayMetrics.xdpi 在任何设备上都是不同的。遗憾的是,这个错误的答案还有最多的赞数。 - toom
1
私有函数 dpToPx(dp: Int) = (dp * Resources.getSystem().displayMetrics.density).toInt() - Raphael C
不是更好,Resources.getSystem()并不总是正确的。 - EpicPandaForce

36

px转dp:

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

7
这是从DP到PX的代码,但是通过以下更正可以实现从PX到DP的转换:typedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX, valueInDp , getResources().getDisplayMetrics());。这也是最佳答案。 - PaNaVTEC
1
@PaNaVTEC,你提到的dp转px的解决方案是不正确的。applyDimension只会返回像素值。请参考https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/core/java/android/util/TypedValue.java#344,其中`COMPLEX_UNIT_PX`没有进行任何转换。 - Stephen Niedzielski
这个答案是完全错误的。它返回的最终值是以像素为单位,而不是 dp。如果你查看源代码,在 TypedValue.COMPLEX_UNIT_DIP 的情况下,返回的是 value * metrics.density,而实际上你需要的是 value * metrics.density。所以,你得到的 valueInDp 并不是 dp,而是 px 中的 valueInPx - bitbybit
^笔误,我是指_"...在你实际需要value / metrics.density的地方"_ - bitbybit

29

只需调用getResources().getDimensionPixelSize(R.dimen.your_dimension)即可将单位从dp转换为像素


3
根据文档,这与getDimension()相同,只是返回值转换为整数像素以便作为大小使用。大小的转换涉及将基础值四舍五入,并确保非零基础值至少为一个像素大小。 - CJBS

14

使用此函数

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

6

使用以下 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()

5
px = dp * (dpi / 160)

dp = px * (160 / dpi)

1
虽然,现在我想起来了... Bram 的原始公式怎么可能会出现除以零的情况呢?displayMetrics.densityDpi 只会是 120、160、240 或 320,永远不会是 0。 - ct_rob
2
(displayMetrics.densityDpi / 160) - 这部分在小设备上可能会得到0,例如它计算的是120/160,两个值都是整数,结果为0。这最终变成了(int)(px/0)。 - user658042
啊,你说得对。所以他只需要在公式中使用 long:160.0。 - ct_rob
这个答案与上一个类似,因为DisplayMetrics.DENSITY_DEFAULT的值为160。但是请问DPI是什么,我们如何计算它? - John smith

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

感谢您的选择。

给定的解决方案中不错的补充。 - Bram
如果转换计算不比映射查找快,更不用说同步查找了,我会感到惊讶。您有任何测试结果来证明这种优化是有益的吗?在 Android 上,由于工作内存有限,除非有充分的理由,否则不应该为了计算而牺牲内存。 - Lorne Laliberte

2
优雅的 Kotlin 解决方案 :)
val Int.dp get() = this / (Resources.getSystem().displayMetrics.densityDpi.toFloat() / DisplayMetrics.DENSITY_DEFAULT)
val Float.dp get() = this / (Resources.getSystem().displayMetrics.densityDpi.toFloat() / DisplayMetrics.DENSITY_DEFAULT)

val Int.px get() = this * Resources.getSystem().displayMetrics.density
val Float.px get() = this * Resources.getSystem().displayMetrics.density

使用方法:

val dpValue = 2.dp
val pxFromDpValue = 2.px

重要提示:

我不确定 Resources.getSystem() 在屏幕方向变化时是否能正常工作。

如果想在片段或活动中使用它,只需将其添加到基础片段或基础活动中,并像这样使用:

abstract class BaseFragment : Fragment() {

    val Int.dp get() = this / (resources.displayMetrics.densityDpi.toFloat() / DisplayMetrics.DENSITY_DEFAULT)
    val Float.dp get() = this / (resources.displayMetrics.densityDpi.toFloat() / DisplayMetrics.DENSITY_DEFAULT)

    val Int.px get() = this * resources.displayMetrics.density
    val Float.px get() = this * resources.displayMetrics.density

    .......
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接