【问题标题】:Putting screen densities into the correct bucket将屏幕密度放入正确的桶中
【发布时间】:2018-08-02 14:44:03
【问题描述】:

一组六个广义密度:

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

来自每个手机显示的 wiki 页面

缩小到存储桶中,即如果 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 中,我有不同的边距,并根据桶大小设置 dp,即

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

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

【问题讨论】:

    标签: android


    【解决方案1】:

    我问的原因是因为我创建了以下值目录资源。 (...) 在 dimens.xml 中,我有不同的边距,并根据存储桶大小设置 dp。 (...) 我很想知道这是否是正确的方法。

    我不确定为什么要根据密度在dp 中指定不同的边距。将边距指定为dp 一次,对于基线密度,已经为您处理了所有其他密度,这意味着在任何设备上显示时,边距的物理尺寸都将相同。

    如果您使用px 而不是dp(但不要),那么您必须自己为不同的屏幕进行缩放。

    缩小到存储桶中,即如果 ppi 为 300 会进入 hdpi 存储桶,因为它小于 320?

    是的,但不是因为它小于 320。如果有经验法则,我会说它是四舍五入到最接近的广义密度。请参阅此图,了解 Android 如何将实际密度大致映射到广义密度(数字不准确):

    the documentation 的相关部分是这样的:

    每个通用尺寸和密度都涵盖一系列实际屏幕尺寸和密度。例如,两台设备都报告屏幕尺寸正常,实际屏幕尺寸和纵横比可能会在手动测量时略有不同。同样,报告屏幕密度为 hdpi 的两个设备的实际像素密度可能略有不同。 Android 将这些差异抽象为应用程序,因此您可以提供为通用尺寸和密度设计的 UI,并让系统根据需要处理任何最终调整。

    同样,如果您只是在编写应用程序,那么您不应该真正关心Android 是如何做到这一点的。你应该关心的是:

    • dpwrap_content/match_parent 中指定所有layout dimension 值,视情况而定(文本可以在sp 中以额外匹配用户偏好,但只能是文本),
    • 根据屏幕的physical sizeorientation 考虑不同的布局,
    • 为不同的densities 提供位图资源,以避免模糊或像素化伪影(因为如果您使用dpwrap_content,Android 会缩放它们以具有正确的物理尺寸)。

    Android 将根据使用中屏幕的实际密度,根据需要对lookup the best matching resourcethen transparently handle 任何dp 单位进行缩放。 dp 单位到屏幕像素的转换很简单:px = dp * (dpi / 160)

    注意 actual 密度与 generalized 密度相对。后者只是方便开发人员,因为不可能为每个屏幕提供可绘制对象。这样,开发人员只需要提供 3 或 4 组图形,而 Android 会选择最合适的并根据特定设备的需要进行进一步调整。 (现在可以使用一个可绘制的矢量而不是许多预先缩放的光栅图形,这意味着更好的质量和更小的尺寸。)

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

    不,不是。根据Google device metrics,您列出的所有设备都比您预期的高:

    Galaxy S3    NA        NA
    Nexus 4     318     xhdpi
    Nexus 5X    424    xxhdpi
    Nexus 5     445    xxhdpi
    Nexus 6     493   xxxhdpi
    Nexus 6P    515   xxxhdpi
    

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

    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 具有更高的物理密度并且距离 640 像素/英寸还很远。 Android One 是 hdpi,但它只比 Nexus 7 '12(tvdpi)稍微密集一些。 Chromebox 30 和 Chromebook Pixel(诚然,不是 Android)被分配到存储桶 mdpixhdpi,即使它们在物理上分别低于 ldpihdpi

    【讨论】:

    • 我认为你解释得很好。尤其是那张非常清晰的图表。
    • 很棒的答案。但是,“注意实际密度而不是广义密度” - 不完全正确。 AFAIK,所有 dp 计算都是在 metrics.Density 上完成的。在我的 Moto E4 上,这是“2”,表示 2x160 = 320 dpi。设备报告的实际像素密度为 Xdpi=294.967,Ydp=295.563,但不带装饰的根视图的宽度和高度 dp 为 360x568; 360 x 2 = 720,即设备宽度(以像素为单位)。所有 dp 测量值均来自该近似密度,而不是实际 XYdpi。最终结果是,如果在不同设备上使用标尺测量,“固定”dp 大小的区域会略有不同。
    • ...因此,就设备设置metrics.Density到准确测量其密度而言,您是正确的;这意味着它可能比设备(此处为 Moto E4)所在的“xhdpi 存储桶”更准确。但是,这取决于设备来报告。在这种情况下,当我输入 E4 的像素和屏幕对角线尺寸时,“密度 = 1.75”(x160 = 280 dpi)是模拟器的默认值。但实际的设备供应商做出了不同的选择。供应商选择更常见的密度的一个优势是应用程序可能只在流行的密度上进行了测试;可能是奇数尺寸的劣质布局。
    • 注意:在 java 中,getResources().getDisplayMetrics().density; 我正在通过 Xamarin C# 进行测试,其中命名转换为 .Net 标准。
    • @ToolmakerSteve,感谢您启发我。确实,calculations are based on DisplayMetrics.densityDpi,似乎遵循DisplayMetrics.density * 160 公式。我已经阅读了太多我引用的文档。当前文档只是说:“系统根据屏幕的 dpi 选择适当的位图。如果您没有为该密度提供特定于密度的资源,系统会选择下一个最佳匹配并将其缩放以适应屏幕”
    【解决方案2】:

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

    你是大部分正确的。

    问题出在这部分:


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

    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 这样的文件夹破坏了。 按照设计,密度像素与设备无关 - 100dpdpi = 240 的设备上看起来与宽/长一样dpi = 480 的设备上。因此,如果您希望您的应用看起来一致,请不要为不同的屏幕密度提供不同的尺寸。

    思考这个问题的正确方法是认识到受不同屏幕密度影响的唯一资源是drawabledrawable 在 dpi = 240 的屏幕上看起来 两倍 与密度 = 480 的屏幕相比。我相信您提供的文件夹像 drawable-hdpidrawable-xhdpi 等. 处理这个。对于其他所有内容,尤其是尺寸,请使用 dp。对于文本大小,请使用scaled-pixels - sp

    更重要的是,您应该担心适用于 android 的不同屏幕尺寸的范围。与 5 英寸手机相比,您将如何在 10 英寸设备上使用所有额外的屏幕空间?您应该对-normal-largexlarge 等限定词更感兴趣。

    总结一下:

    • 考虑所有具有相同屏幕尺寸的设备 - 使用density pixels 时,它们的屏幕密度无关紧要。
    • 对于您使用的每个drawable 资源,请将其缩放版本放入您希望支持的存储桶中。请记住,如果您不为某个存储桶提供资源(例如drawable-hdpi),android 将从drawable-xhdpi 文件夹缩小您的可绘制对象(前提是已定义drawable-xhdpi)。反之亦然:如果您已将所有可绘制对象放在drawable-xhdpi 中,android 将在xxhdpi 设备上放大您的可绘制对象。结果将是模糊的图形 - 因为放大了。

    我知道这里的坡度有点陡:)。因此,如果您需要进一步澄清,请给我留言。

    【讨论】:

    • 只有当布局在px 中指定它的大小时,一个drawable 才会看起来两倍大,否则Android 会将它缩放到相同大小,但图像可能会模糊。像drawable-hdpi 这样的文件夹的原因是图形的物理尺寸不合适,而是让它们在不同设备上以高质量显示。
    【解决方案3】:

    来自 Android 文档:

    Supporting Multiple Screens

    在某些情况下,您需要以 dp 表示尺寸,然后 将它们转换为像素。想象一个应用程序,其中滚动或 在用户的手指移动了 at 至少 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 班级。

    【讨论】:

      【解决方案4】:

      您必须以 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

      【讨论】:

        【解决方案5】:

        谈到 Android 设备时,PPI == DPI

        PPI(每英寸像素数)和 DPI(每英寸点数)含义相同,因为它们是屏幕密度;因此,您关于 PPI 与 DPI 的方法是正确的。

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

        屏幕密度被引用为 Pixels Per Inch,PPI,是 适合一英寸的像素数。那么数字越高 更清晰的图像在显示器上看起来,因此消费者认为 购买设备时,高 PPI 是一个优势。有时 图被引用为 Dots Per Inch、DPI ...

        【讨论】:

        • OP 的方法仅在 ppi 表示 dpi 的方式上是正确的。否则,有很多事情应该讨论。
        【解决方案6】:

        有点晚了,但这可能对其他读者有用。屏幕密度和 Android 用来从中挑选资源的“桶”可能会令人困惑。 Google 的文档非常密集,在编写代码时需要大量工作才能将其提炼成有用的东西。部分是因为有几个因素,他们告诉你关于屏幕密度和倾角的一切。但简短的回答是这样的。

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

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

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

        接下来我有一个简单的设备指标方法来告诉我有关屏幕的信息。 (注意,我使用的是木材记录器)。

        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 年的 this video 在今天仍然适用。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2011-12-24
          • 2021-01-17
          • 1970-01-01
          • 2011-07-21
          • 1970-01-01
          • 1970-01-01
          • 2019-12-23
          相关资源
          最近更新 更多