将屏幕密度放入正确的桶中

36

一组六个广义密度函数:

ldpi (low) ~120dpi
mdpi (medium) ~160dpi
hdpi (high) ~240dpi
xhdpi (extra-high) ~320dpi
xxhdpi (extra-extra-high) ~480dpi
xxxhdpi (extra-extra-extra-high) ~640dpi

来自每个手机显示器的维基页面

将其缩小到桶中,即如果ppi为300,则会放入hdpi桶中,因为它小于320?

Galaxy S3 306ppi -> bucket hdpi
Nexus 4  318ppi -> bucket hdpi
Nexus 5  445ppi -> bucket xhdpi
Nexus 5X 432ppi -> bucket xhdpi
Nexus 6  493ppi -> bucket xxhdpi 
Nexus 6P 518ppi -> bucket xxhdpi

这是计算屏幕尺寸的正确方法吗?

我之所以问是因为我已经创建了以下数值目录资源:

values-hdpi/dimens
values-xhdpi/dimens
values-xxhdpi/dimens
values-xxxhdpi/dimens

dimens.xml文件中,我有不同的边距并根据bucket大小设置dp。

<dimen name="network_quantity_margin_top">100dp</dimen>

我想知道这是否是正确的做法。

6个回答

46
我之所以问这个问题是因为我创建了以下价值目录资源。(...)在dimens.xml中,我有不同的边距,并根据桶大小设置dp。(...)我想知道这是否是正确的方法。
我不确定为什么要根据密度指定不同的dp边距。一次将边距指定为dp,适用于基线密度,已经为您处理了所有其他密度,这意味着当在任何设备上显示时,边距的物理大小都将相同。
如果您使用px而不是dp(但不要这样做),则必须为不同的屏幕自行进行缩放。
缩小到桶内,即如果ppi为300,则会进入hdpi桶,因为它小于320吗?
是的,但不是因为它小于320。如果有一个经验法则,我会说它是四舍五入到最近的广义密度。请参见Android大致将实际密度映射到广义密度的示例(图不精确):

generalized densities

文档的相关部分如下:

每个通用尺寸和密度都涵盖了一定范围的实际屏幕尺寸和密度。例如,报告为normal屏幕尺寸的两个设备,在手动测量时可能具有略微不同的实际屏幕尺寸和宽高比。同样,报告为hdpi屏幕密度的两个设备可能具有略微不同的实际像素密度。Android将这些差异抽象成应用程序可以使用的通用尺寸和密度,让系统根据需要进行任何最终调整。

因此,如果你只是开发一个应用程序,你不需要真正关心Android是如何做到这一点的。你需要关心的是:

  • 请将所有布局尺寸的值用dp或适当使用wrap_content/match_parent指定(文本可以使用sp以便与用户偏好匹配,但不能使用除文本以外的内容),
  • 考虑不同的布局,根据屏幕的物理尺寸方向而定,
  • 提供位图资源,以适应不同的密度,以避免模糊或像素化的伪影(因为如果您使用dpwrap_content,Android会对它们进行缩放以获得正确的物理大小)。

Android将会查找最佳匹配资源, 并根据实际使用的屏幕密度,透明地处理dp单位的任何缩放。将dp单位转换为屏幕像素非常简单:px = dp * (dpi / 160)

请注意实际密度而不是一般化的密度。后者仅供开发人员方便使用,因为不可能为每个屏幕提供可绘制对象。这样开发人员只需要提供3或4组图形,而Android会选择最接近的匹配并进一步调整以适应该特定设备的需求。(现在可以使用一个矢量可绘制对象代替许多预缩放光栅图形,这意味着更好的质量和更小的尺寸。)

这是计算屏幕尺寸桶的正确方法吗?

不是的。根据Google设备指标,你列出的所有设备都属于比你预期更高的桶位。
Galaxy S3    NA        NA
Nexus 4     318     xhdpi
Nexus 5X    424    xxhdpi
Nexus 5     445    xxhdpi
Nexus 6     493   xxxhdpi
Nexus 6P    515   xxxhdpi

我从列表中选出了一些其他设备,并绘制了这些设备根据其实际物理密度落入密度桶的不同情况。

density buckets of some Android devices

Chromebox 30            101      mdpi
Chromebook 11           135      mdpi
Samsung Galaxy Tab 10   149      mdpi
Nexus 7 '12             216     tvdpi
Android One             218      hdpi
Chromebook Pixel        239     xhdpi
Nexus 9                 288     xhdpi
Nexus 10                299     xhdpi
Moto X                  312     xhdpi
Nexus 4                 318     xhdpi
Nexus 7 '13             323     xhdpi 
Moto G                  326     xhdpi
Dell Venue 8            359     xhdpi
LG G2                   424    xxhdpi
Nexus 5X                424    xxhdpi
HTC One M8              441    xxhdpi
Nexus 5                 445    xxhdpi
Nexus 6                 493   xxxhdpi
Nexus 6P                515   xxxhdpi
LG G3                   534    xxhdpi

您可以看到,除了一些显著的例外情况外,最接近的广义密度被选择的规则仍然适用。这些例外是Nexus 6和6P,它们被列为“xxxhdpi”,即使LG G3具有更高的物理密度,但仍远远低于640px/in。Android One是“hdpi”,但它只比Nexus 7 '12略密,后者是“tvdpi”。Chromebox 30和Chromebook Pixel(尽管不是Android)分配到“mdpi”和“xhdpi”桶中,即使它们在物理上低于“ldpi”和“hdpi”,分别是如此。

我认为你解释得非常好,特别是那张图表非常清晰。 - ant2009
非常好的答案。然而,"请注意实际密度与广义密度的区别" - 不完全正确。据我所知,所有dp计算都是基于metrics.Density完成的。在我的Moto E4上,它是"2",这意味着2x160 = 320 dpi。设备报告的实际像素密度为Xdpi=294.967,Ydp=295.563,但是没有装饰的根视图的宽度和高度dp为360x568;360 x 2 = 720,这是设备的像素宽度。所有dp测量都是从该近似密度派生出来的,而不是实际的XYdpi。最终结果是,如果在不同的设备上使用标尺测量,则“固定”dp大小的区域会略有变化。 - ToolmakerSteve
因此,您的观点是正确的,设备设置指标。密度为其密度的准确测量;这意味着它可能比设备(此处为Moto E4)所在的“xhdpi bucket”更准确。但是,这取决于设备报告。在这种情况下,“Density = 1.75”(x160 = 280 dpi)是模拟器在我输入E4的像素和屏幕对角线尺寸时默认的值。但实际设备供应商做出了不同的选择。选择更常见的密度的一个优点是,应用程序可能仅在流行密度上进行了测试;在奇怪的大小上可能会有较差的布局。 - ToolmakerSteve
注意:在 Java 中,这将是 getResources().getDisplayMetrics().density; 我正在使用 Xamarin C# 进行测试,其中名称已转换为 .Net 标准。 - ToolmakerSteve
@ToolmakerSteve,感谢您的启示。确实,计算是基于DisplayMetrics.densityDpi,这似乎遵循了DisplayMetrics.density * 160公式。我对我引用的文档过于深入地阅读了。当前文档只是说:“系统根据屏幕dpi选择适当的位图。如果您没有为该密度提供特定于密度的资源,则系统会选择下一个最佳匹配项并将其缩放以适合屏幕”。 - arekolek
我之前的评论应该使用永久链接,而不是链接到“master”。 - arekolek

10

我想知道这是否是正确的做法。

你大部分是正确的。

问题出在这里:


我之所以问,是因为我已经创建了以下价值目录资源:

values-hdpi/dimens
values-xhdpi/dimens
values-xxhdpi/dimens
values-xxxhdpi/dimens
在dimens.xml文件中,我设置了不同的边距,并根据桶大小设置dp。
<dimen name="network_quantity_margin_top">100dp</dimen>
dp 的作用被 values-hdpi/dimens 这样的文件夹定义所破坏了。密度无关像素(Density Pixels) 是设备无关的设计,例如在一个 dpi = 240 的设备上,100dp 看起来和在 dpi = 480 的设备上一样宽/长。因此,如果你想让你的应用看起来一致,请不要为不同的屏幕密度提供不同的尺寸。
正确的思考方式是意识到唯一受不同屏幕密度影响的资源是 drawable。在 dpi = 240 的屏幕上,drawable 看起来会比 density = 480 的屏幕大两倍。我相信你正在提供像 drawable-hdpidrawable-xhdpi 这样的文件夹来处理这个问题。对于其他所有内容,特别是尺寸,请使用 dp。对于文本大小,请使用 scaled-pixels - sp
更重要的是,你应该担心 Android 上可用的不同屏幕尺寸的范围。如何在10英寸的设备上使用额外的屏幕空间与5英寸的手机相比?像 -normal-largexlarge 这样的限定符应该更受你的关注。
总之:
- 将所有特定屏幕尺寸的设备视为相同 - 在使用 Density Pixels 时,它们的屏幕密度是无关紧要的。 - 对于每个 drawable 资源,将它们的缩放版本放在您希望支持的桶中。请记住,如果您没有为某个存储桶提供资源(例如 drawable-hdpi),Android 将从 drawable-xhdpi 文件夹中缩小您的图形(前提是定义了 drawable-xhdpi)。反之亦然:如果您将所有可用的绘制资源都放在 drawable-xhdpi 中,Android 将在 xxhdpi 设备上放大您的图形。结果将会是模糊的图形 - 因为被放大了。
我知道这可能有点陡峭 : )。如果你需要澄清一些问题,请给我留言。

1
如果布局在px中指定了可绘制对象的大小,它将看起来比实际尺寸大两倍,否则Android将缩放它以具有相同的大小,但图像可能会模糊。像drawable-hdpi这样的文件夹之所以存在是因为没有正确的图形物理尺寸,但是要在各种设备上高质量地显示它们。 - arekolek

6

来自Android文档:

支持多屏幕

在某些情况下,您需要以dp为单位表示尺寸,然后将其转换为像素。想象一下一个应用程序,在用户的手指移动至少16个像素之后识别滚动或轻扫手势。在基准屏幕上,用户必须移动16个像素/160 dpi,相当于1/10英寸(或2.5毫米),然后才能识别手势。在具有高密度显示(240dpi)的设备上,用户必须移动16个像素/240 dpi,相当于1/15英寸(或1.7毫米)。距离更短,因此应用程序对用户更加敏感。

要解决此问题,手势阈值必须在代码中以dp表示,然后转换为实际像素。例如:

// The gesture threshold expressed in dp
private static final float GESTURE_THRESHOLD_DP = 16.0f;

// Get the screen's density scale
final float scale = getResources().getDisplayMetrics().density;
// Convert the dps to pixels, based on density scale
mGestureThreshold = (int) (GESTURE_THRESHOLD_DP * scale + 0.5f);

// Use mGestureThreshold as a distance in pixels...

DisplayMetrics.density这个字段指定了你必须使用的比例因子来将dp单位转换为像素,根据当前屏幕密度。在中等密度屏幕上,DisplayMetrics.density等于1.0;在高密度屏幕上它等于1.5;在超高密度屏幕上它等于2.0;在低密度屏幕上它等于0.75。这个数字是你应该乘以dp单位的因子,以便获得当前屏幕的实际像素数。(然后加上0.5f,四舍五入到最接近的整数,当转换为整数时。)有关更多信息,请参阅DisplayMetrics类。


2

您需要处理屏幕宽度的资源,使用dp而不是dpi(每英寸点数)

例如Nexus 5具有1920 X 1080像素和480 dpi,它使用xxhdpi,Nexus 6P具有2560 X 1440像素和560 dpi,也使用xxhdpi而不是xxxhdpi!

为了处理它,请使用最小宽度资源“drawable-swXXXdp”


dp中的宽度=像素中的宽度/(dpi / 160) dp = 1440 /(560/160)=约411

create drawable-sw411dp 

Reference


1

当谈到安卓设备时,PPI == DPI

PPI(每英寸像素数)和DPI(每英寸点数)是同一个意思,因为它们都是屏幕密度的一种度量方式;因此,您对于PPI vs DPI的看法是正确的。

可以在这里找到详细的解释。文章的关键部分如下:

屏幕密度以每英寸像素数(PPI)表示,即每英寸可容纳的像素数。数字越高,则显示器上的图像看起来越清晰,因此消费者在购买设备时认为高PPI值是一种优势。有时数字被引用为每英寸点数(DPI)...


2
OP的方法只在ppi意味着dpi的情况下是正确的。否则,还有很多需要讨论的地方。 - arekolek

0

虽然有点晚,但这对其他读者可能有用。屏幕密度和 Android 用于选择资源的“桶”可能会让人感到困惑。谷歌的文档非常复杂,编写代码时需要花费大量精力将其提炼成有用的东西。部分原因是因为有几个因素,它们告诉你关于屏幕密度和 dips 的一切。但简短的答案是这样的。

基本上,dpi 是您的定义因素(如果您依赖其他因素,如小/中/大),那么这不是您的答案。否则,我发现this的答案非常有帮助和简单。这里是我从各种来源整理出来的一些代码,我在应用程序启动时运行它们以确定显示信息。

屏幕密度告诉我设备支持的 dpi 级别。

float density = context.getResources().getDisplayMetrics().density;

接下来我有一个简单的设备度量方法,可以告诉我关于屏幕的信息。(注意,我正在使用Timber日志记录器)。

protected static void checkDeviceSize(AppCompatActivity context) {
    DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
    Display display = context.getWindowManager().getDefaultDisplay();
    DisplayMetrics outMetrics = new DisplayMetrics();
    display.getMetrics(outMetrics);

    float density = context.getResources().getDisplayMetrics().density;
    float dpHeight = outMetrics.heightPixels / density;
    float dpWidth = outMetrics.widthPixels / density;
    String dpiName = getDpiName(density);
    Timber.e("density  :" + density + " [" + dpiName + "]");
    Timber.e("height dp:" + dpHeight + ", (" +outMetrics.heightPixels + "px)");
    Timber.e("width dp :" + dpWidth + ", (" + outMetrics.widthPixels + "px)");

}

我还有一个简单的辅助方法,用于确定支持上述方法的DPI名称。

public static final String DPI_LDPI = "ldpi";
public static final String DPI_MDPI = "mdpi";
public static final String DPI_HDPI = "hdpi";
public static final String DPI_XHDPI = "xhdpi";
public static final String DPI_XXHDPI = "xxhdpi";
public static final String DPI_XXXHDPI = "xxxhdpi";
public static final String DPI_TVDPI = "tvdpi";

private static String getDpiName(float density) {
    String result = "undefined";
    if (density < 1.0) {
        result = DPI_LDPI;
    } else if (density == 1.0f) {
        result = DPI_MDPI;
    } else if (density <= 1.3f) {
        result = DPI_TVDPI;
    } else if (density <= 1.5f) {
        result = DPI_HDPI;
    } else if (density <= 2.0f) {
        result = DPI_XHDPI;
    } else if (density <= 3.0f) {
        result = DPI_XXHDPI;
    } else if (density <= 4.0f) {
        result = DPI_XXXHDPI;
    }
    return result;
}

最后,这个视频从2013年到今天仍然具有相关性。


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