支持不同的像素密度

Android 设备不仅有不同的屏幕尺寸,手机、平板电脑、电视 等,但也有一些屏幕具有不同的像素尺寸。一个 设备可能每英寸有 160 像素,而另一种设备适合 480 像素 同一空间内的像素。如果您未考虑这些变化 像素密度,系统可能会按比例调整 导致图片模糊不清或 以错误的尺寸显示

本页将向您介绍如何设计您的应用以支持 使用与分辨率无关的测量单位来调整像素密度 并为每种像素密度提供备用位图资源

请观看以下视频,简要了解这些方法。

如需详细了解如何设计图标资源,请参阅 Material Design 图标指南

使用密度无关像素

避免使用像素来定义距离或尺寸。定义维度 因为不同的屏幕具有不同的像素密度 因此,相同数量的像素 不同设备

<ph type="x-smartling-placeholder">
</ph> 一张图片,显示密度不同的两个示例设备显示屏 <ph type="x-smartling-placeholder">
</ph> 图 1:大小相同的两个屏幕可以具有不同数量的像素。

保持界面的可见大小 在不同密度的屏幕上时,使用 将密度无关像素 (dp) 作为度量单位。1 dp 表示 虚拟像素单位,在中密度屏幕上大致等于 1 个像素 (160 dpi,或“基准”密度)。Android 会将此值转换为 为每种密度提供适当的实际像素数。

以图 1 中的两台设备为例。符合以下条件的视图: 宽度为 100 像素的设备在左侧显示时要大得多。观看次数 定义为 100 dp 宽,在两个屏幕上具有相同的尺寸。

定义文字大小时,您可以改用可扩展的 像素 (sp) 作为单位。sp 单位为 大小与 dp 相同,但是会根据用户的首选 文字大小。切勿将 sp 用于布局尺寸。

例如,如需指定两个视图的间距,请使用 dp:

<Button android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@string/clickme"
    android:layout_marginTop="20dp" />

指定文本大小时,请使用 sp:

<TextView android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:textSize="20sp" />

将 dp 单位转换为像素单位

在某些情况下,您需要以 dp 表示尺寸,然后 将其转换为像素。dp 单位到屏幕像素的转换 如下所示:

px = dp * (dpi / 160)

注意:请勿通过硬编码此公式来计算像素,而应使用 TypedValue.applyDimension(), 它可将许多类型的尺寸(dp、sp 等)转换为像素。

假设有一个应用可以识别滚动或滑动手势 当用户的手指至少移动 16 像素之后。在基准水平 屏幕上,用户的手指必须先移动 16 pixels / 160 dpi,即 1 英寸的 1/10(或 2.5 毫米) 手势识别。

在设备上 配备高密度显示屏 (240 dpi),用户的手指必须移动 16 pixels / 240 dpi, 等于 1/15 英寸(或 1.7 毫米)。这个距离要短得多, 因此应用对用户来说似乎更灵敏。

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

Kotlin

// The gesture threshold expressed in dp
private const val GESTURE_THRESHOLD_DP = 16.0f

private var gestureThreshold: Int = 0

// Convert the dps to pixels, based on density scale
gestureThreshold = TypedValue.applyDimension(
  COMPLEX_UNIT_DIP,
  GESTURE_THRESHOLD_DP + 0.5f,
  resources.displayMetrics).toInt()

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

Java

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

// Convert the dps to pixels, based on density scale
int gestureThreshold = (int) TypedValue.applyDimension(
  COMPLEX_UNIT_DIP,
  GESTURE_THRESHOLD_DP + 0.5f,
  getResources().getDisplayMetrics());

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

DisplayMetrics.density 字段 指定用于将 dp 单位转换为 像素。在中等密度屏幕上 DisplayMetrics.density等于 在高密度屏幕上,此值等于 1.5。在超高密度屏幕上 等于 2.0;在低密度屏幕上,等于 0.75。此数字为 由 TypedValue.applyDimension() 用于 用于获取当前屏幕的实际像素数。

使用预缩放的配置值

您可以使用 ViewConfiguration 类访问常用的 距离、速度和时间例如, 距离(以像素为单位),以获取滚动阈值 使用 getScaledTouchSlop()

Kotlin

private val GESTURE_THRESHOLD_DP = ViewConfiguration.get(myContext).scaledTouchSlop

Java

private final int GESTURE_THRESHOLD_DP = ViewConfiguration.get(myContext).getScaledTouchSlop();

ViewConfiguration 中以 getScaled 前缀开头的方法 返回的值(以像素为单位),无论当前 像素密度。

首选矢量图形

除了创建多个特定于密度的图片版本之外,另一种方法是 创建一个矢量图形。矢量图形使用 XML 创建图像, 定义路径和颜色,而不是使用像素位图。因此,矢量 可以缩放到任意尺寸,而不会出现缩放失真 最适合图标,而不是照片等插图。

矢量图形通常以 SVG(可缩放矢量图形)文件的形式提供, 但 Android 不支持此格式,因此您必须将 SVG 文件转换为 Android 的矢量 drawable 格式。

您可以使用 Android Studio 的 Vector Asset Studio 如下所示:

  1. Project 窗口中,右键点击 res 目录,然后选择 新建 >Vector Asset
  2. 选择 Local file (SVG, PSD)
  3. 找到要导入的文件并进行任何调整。

    <ph type="x-smartling-placeholder">
    </ph> 一张图片,展示如何在 Android Studio 中导入 SVG <ph type="x-smartling-placeholder">
    </ph> 图 2:使用 Android Studio。

    您可能会注意到 Asset Studio 窗口中出现了一些错误 表示矢量可绘制对象不支持文件的某些属性。 这不会阻止您导入文件;不受支持的属性 将会被忽略。

  4. 点击 Next

  5. 在下一个屏幕上,确认您要将项目中的文件放在哪个源代码集上 然后点击完成

    由于可以对所有像素密度使用一个矢量可绘制对象,因此此文件 放入默认的可绘制对象目录,如以下代码所示 层级结构。您不需要使用密度特定的目录。

    res/
      drawable/
        ic_android_launcher.xml
    

如需详细了解如何创建矢量图形,请阅读矢量可绘制对象 文档。

提供备用位图

为了在像素密度不同的设备上提供良好的图形质量, 为应用中的每个位图提供多个版本,每个版本各一个 以及相应的分辨率否则,Android 必须扩展 这样位图会在每个屏幕上占用相同的可见空间 例如模糊处理等缩放伪影

<ph type="x-smartling-placeholder">
</ph> 一张显示不同密度大小的位图相对尺寸的图片 <ph type="x-smartling-placeholder">
</ph> 图 3:不同密度级别的位图的相对尺寸。

您的应用中有多个密度级别可供使用。表 1 介绍了可用的不同配置限定符以及 适用的政策

表 1. 针对不同服务 像素密度。

密度限定符 说明
ldpi 适用于低密度 (ldpi) 屏幕 (~ 120 dpi) 的资源。
mdpi 适用于中密度 (mdpi) 屏幕 (~ 160 dpi) 的资源这是基准 密度。
hdpi 适用于高密度 (hdpi) 屏幕 (~ 240 dpi) 的资源。
xhdpi 适用于超高密度 (xhdpi) 屏幕 (~ 320 dpi) 的资源。
xxhdpi 适用于超超高密度 (xxhdpi) 屏幕 (~ 480 dpi) 的资源。
xxxhdpi 适用于超超超高密度 (xxxhdpi) 屏幕 (~ 640 dpi) 的资源。
nodpi 适用于所有密度的资源。这些是与密度无关的资源。系统不会 缩放带有此限定符的资源,而不考虑当前屏幕的密度。
tvdpi 适用于密度介于 mdpi 和 hdpi 之间的屏幕的资源;大约 约 213 dpi。这不属于“主要”密度组。主要预期用途 而大多数应用都不需要它,因此可以提供 mdpi 和 hdpi 资源足以满足大多数应用的需求,且系统会按需求扩缩资源, 适当的选择。如果您发现有必要提供tvdpi资源, 按 1.33 * mdpi 的系数调整图片大小。例如,一个 100x100 像素的图片 对于 tvdpi,mdpi 屏幕为 133x133 像素。

要针对不同密度创建备用可绘制位图资源,请遵循 六种主要密度之间的缩放比例为 3:4:6:8:12:16。例如,如果你的 一个位图可绘制对象,对于中密度屏幕,为 48x48 像素,尺寸为:

  • 36x36 (0.75x) 用于低密度 (ldpi)
  • 48x48(1.0x 基准)- 中密度 (mdpi)
  • 72x72 (1.5x) - 高密度 (hdpi)
  • 96x96 (2.0x) - 超高密度 (xhdpi)
  • 144x144 (3.0x) - 超超高密度 (xxhdpi)
  • 192x192 (4.0x) - 超超超高密度 (xxxhdpi)

将生成的图片文件放在相应的子目录中 在 res/ 下:

res/
  drawable-xxxhdpi/
    awesome_image.png
  drawable-xxhdpi/
    awesome_image.png
  drawable-xhdpi/
    awesome_image.png
  drawable-hdpi/
    awesome_image.png
  drawable-mdpi/
    awesome_image.png

然后,每当您引用 @drawable/awesomeimage 时, 系统会根据屏幕的 dpi 选择相应的位图。如果您 没有为该密度提供特定于密度的资源,则系统会找到 下一个匹配的高度,并按比例进行缩放以适应屏幕。

提示:如果您有可绘制资源 您不希望系统进行扩缩,例如,在执行 在运行时自行对图片进行一些调整, 包含 nodpi 配置限定符的目录。 带有此限定符的资源会被视为与密度无关, 系统不会对其进行扩缩

如需详细了解其他配置限定符和 Android 如何选择合适的资源来 当前屏幕配置,请参阅应用资源概览

将应用图标放在 mipmap 目录中

与其他位图资源一样,对于每个位图资源, 您的应用图标。不过,有些应用启动器显示的应用图标最多会有 25% 的比例 大于设备的密度级别所要求的值。

例如,如果设备的密度级别为 xxhdpi, drawable-xxhdpi 中,应用启动器会放大此图标, 这样会显得不太清晰

为避免出现这种情况, 您的应用图标放在 mipmap 目录(而不是 drawable 目录)中。取消点赞 drawable 目录中,所有 mipmap 目录都会保留在 APK 中,即使 。这样,启动器应用就能选出最佳应用 分辨率图标。

res/
  mipmap-xxxhdpi/
    launcher_icon.png
  mipmap-xxhdpi/
    launcher_icon.png
  mipmap-xhdpi/
    launcher_icon.png
  mipmap-hdpi/
    launcher_icon.png
  mipmap-mdpi/
    launcher_icon.png

在前面的 xxhdpi 设备示例中,您可以提供一个 mipmap-xxxhdpi 目录中的高密度启动器图标。

如需了解图标设计准则,请参阅系统图标

如需构建应用图标方面的帮助,请参阅使用 Image Asset Studio 创建应用图标

针对不常见的密度问题给出的建议

本部分介绍了 Android 如何对位图执行缩放 以及如何进一步控制 位图以不同的密度绘制除非您的应用会操纵图形 或者,您在以不同的像素密度运行时遇到问题, 可以忽略此部分。

为了更好地了解在处理 运行时,您需要了解系统如何帮助确保正确缩放位图。 这可以通过以下方式实现:

  1. 预缩放资源(如可绘制位图资源)

    根据当前屏幕的密度,系统将使用任何特定于密度的屏幕 从您的应用获取资源如果以下集群中没有资源可用: 密度设置正确时,系统会加载默认资源,并根据需要将其放大或缩小。系统会假定默认资源 目录)专为基准测试而设计 像素密度 (mdpi),并将这些位图调整为 当前像素密度。

    如果您请求预缩放的资源的尺寸,系统将返回值 表示缩放后的尺寸。例如,设计为 50x50 像素的位图 mdpi 屏幕在 hdpi 屏幕上放大为 75x75 像素(如果没有备用资源) 代表 hdpi),并且系统会这样报告尺寸。

    在某些情况下,您可能不希望 Android 预缩放 资源。避免预扩缩的最简单方法是将资源放在资源目录中 替换为 nodpi 配置限定符。例如:

    res/drawable-nodpi/icon.png

    当系统使用此文件夹中的 icon.png 位图时,不会对其进行缩放 根据当前设备密度确定尺寸

  2. 自动调整像素尺寸和坐标

    您可以通过设置 android:anyDensity 来停用预缩放尺寸和图片 设置为 "false",或者针对 Bitmap 以编程方式将 inScaled 设置为 "false"。在 在这种情况下,系统会自动缩放所有绝对像素坐标和 尺寸值。这样做是为了确保 屏幕元素仍会以大致相同的物理尺寸显示 确保它们能以基线像素密度 (mdpi) 显示。系统会负责处理 此缩放操作对应用是透明的,并报告缩放后的像素 而不是实际的像素尺寸

    例如,假设某设备具有 WVGA 高密度屏幕,其尺寸为 480x800, 屏幕大小与传统 HVGA 屏幕相同 - 但它运行的应用已停用 预缩放。在这种情况下,系统会“依赖于”当应用查询屏幕时 尺寸和报告为 320x533,这是像素密度对应的 mdpi 平移的近似值。

    然后,当 应用执行绘图操作,例如使 (10,10) 到 (100, 100),系统会通过缩放相应量来改变坐标,而实际上 会使区域 (15,15) 至 (150, 150) 失效。如果存在以下情况,这种差异可能会导致意外行为: 您的应用会直接操控缩放的位图,但这被认为 以确保实现最佳应用性能。如果您遇到这种情况 请阅读将 dp 单位转换为像素 单位

    通常,您不会停用预缩放。支持多种 遵循本页介绍的基本技术。

如果您的应用操控位图或直接与屏幕上的像素互动 您可能需要采取额外步骤来支持 像素密度。例如,如果您通过计数 您需要对手指覆盖的像素数使用适当的值 与密度无关的像素值,而不是实际像素, 在 dp 和 px 值之间进行转换

针对所有像素密度测试

在采用不同像素的多部设备上测试应用 以便确保界面正确缩放。在实体设备上进行测试 设备;请使用 Android 模拟器。 以适应各种不同的像素密度。

如果您想在实体设备上进行测试 不想购买设备,您可以使用 Firebase 测试实验室 访问 Google 数据中心内的设备。