Android 设备不仅具有不同的屏幕尺寸(手机、平板电脑、电视等),而且具有不同像素尺寸的屏幕。一台设备的每英寸像素数可能为 160,而另一台设备在同一空间内可以容纳 480 像素。如果不考虑像素密度的这些变化,系统可能会缩放图片,导致图片模糊不清,或者图片可能以错误的尺寸显示。
本页将向您介绍如何设计应用以支持不同的像素密度,方法是使用分辨率无关度量单位,并针对每种像素密度提供备用位图资源。
请观看以下视频,简要了解这些技巧。
如需详细了解如何设计图标资源,请参阅 Material Design 图标指南。
使用密度无关像素
避免使用像素来定义距离或尺寸。使用像素定义尺寸会带来问题,因为不同的屏幕具有不同的像素密度,因此同样数量的像素在不同设备上对应不同的物理尺寸。
![一张图片,其中展示了两个密度不同的示例设备显示屏](https://developer.android.google.cn/static/images/screens_support/densities-phone_2x.png?authuser=5&hl=nb)
如需在密度不同的屏幕上保持界面的可见尺寸,请使用密度无关像素 (dp) 作为衡量单位来设计界面。1 dp 是一个虚拟像素单位,大致等于中密度屏幕(160 dpi,即“基准”密度)上的 1 个像素。对于每种密度,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/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.0,在高密度屏幕上,等于 1.5。在超高密度屏幕上,它等于 2.0,在低密度屏幕上,等于 0.75。TypedValue.applyDimension()
使用此数字获取当前屏幕的实际像素数。
使用预缩放的配置值
您可以使用 ViewConfiguration
类来获取 Android 系统常用的距离、速度和时间。例如,可通过 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 的矢量可绘制对象格式。
您可以使用 Android Studio 的 Vector Asset Studio 将 SVG 转换为矢量可绘制对象,如下所示:
- 在 Project 窗口中,右键点击 res 目录,然后依次选择 New > Vector Asset。
- 选择 Local file (SVG, PSD)。
找到要导入的文件并进行任何调整。
图 2:使用 Android Studio 导入 SVG。 您可能会注意到 Asset Studio 窗口中出现一些错误,指出矢量可绘制对象不支持文件的某些属性。这不会阻止您导入文件;不受支持的属性会被忽略。
点击 Next。
在下一个屏幕上,确认您希望从中查找项目文件的源代码集,然后点击 Finish。
由于一个矢量可绘制对象可用于所有像素密度,因此该文件位于您的默认 drawables 目录中,如以下层次结构所示。您无需使用密度专用目录。
res/ drawable/ ic_android_launcher.xml
如需详细了解如何创建矢量图形,请参阅矢量可绘制对象文档。
提供备用位图
为了在像素密度不同的设备上提供良好的图形质量,请在您的应用中提供每个位图的多个版本,每个版本对应一种密度级别且具有相应分辨率。否则,Android 必须缩放位图,使其在每个屏幕上占据相同的可见空间,从而导致缩放失真,例如模糊。
![显示不同密度大小的位图的相对尺寸的图片](https://developer.android.google.cn/static/images/screens_support/devices-density_2x.png?authuser=5&hl=nb)
您的应用中有多个密度级别可供使用。表 1 介绍了可用的不同配置限定符及其适用的屏幕类型。
表 1. 适用于不同像素密度的配置限定符。
密度限定符 | 说明 |
---|---|
ldpi |
适用于低密度 (ldpi) 屏幕(约 120dpi)的资源。 |
mdpi |
适用于中密度 (mdpi) 屏幕(约 160dpi)的资源。这是基准密度。 |
hdpi |
适用于高密度 (hdpi) 屏幕(约 240dpi)的资源。 |
xhdpi |
适用于超高密度 (xhdpi) 屏幕(约 320dpi)的资源。 |
xxhdpi |
适用于超超高密度 (xxhdpi) 屏幕(约 480dpi)的资源。 |
xxxhdpi |
适用于超超超高密度 (xxxhdpi) 屏幕(约 640dpi)的资源。 |
nodpi |
适用于所有密度的资源。这些是与密度无关的资源。无论当前屏幕的密度如何,系统都不会缩放以此限定符标记的资源。 |
tvdpi |
适用于密度介于 mdpi 和 hdpi 之间的屏幕(约约 213 dpi)的资源。这不属于“主要”密度组。它主要用于电视,而大多数应用都不需要它。对于大多数应用而言,提供 mdpi 和 hdpi 资源便已足够,系统将视情况对其进行缩放。如果发现有必要提供 tvdpi 资源,请将其大小调整为 1.33 * mdpi。例如,mdpi 屏幕的 100x100 像素图像对应于 tvdpi 的 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 中,即使您构建特定于密度的 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 如何在不同像素密度下对位图执行缩放,以及如何进一步控制位图在不同密度下的绘制方式。除非您的应用会操控图形,或者您在不同像素密度上运行时遇到问题,否则您可以忽略此部分。
为了更好地了解在运行时操作图形时如何支持多种密度,您需要了解系统如何有助于确保位图的适当缩放。这是通过以下方法完成的:
- 资源(例如可绘制位图资源)的预缩放
根据当前屏幕的密度,系统会使用您的应用中提供的任何密度特定资源。如果没有具有正确密度的资源,系统会加载默认资源,并根据需要将其放大或缩小。系统假定默认资源(来自没有配置限定符的目录的资源)是针对基准像素密度 (mdpi) 设计的,并会将这些位图调整为适合当前像素密度的大小。
如果您请求预缩放的资源的尺寸,系统将返回表示缩放后尺寸的值。例如,针对 mdpi 屏幕设计的 50x50 像素位图在 hdpi 屏幕上会扩展为 75x75 像素(如果没有适用于 hdpi 的备用资源),并且系统会这样报告大小。
在某些情况下,您可能不希望 Android 预缩放资源。要避免预扩缩,最简单的方法是将资源放在带有
nodpi
配置限定符的资源目录中。例如:res/drawable-nodpi/icon.png
当系统使用此文件夹中的
icon.png
位图时,不会根据当前设备密度对其进行缩放。 - 自动缩放像素尺寸和坐标
您可以停用预缩放尺寸和图片,方法是在清单中将
android:anyDensity
设置为"false"
;或者针对Bitmap
,以编程方式将inScaled
设置为"false"
。在这种情况下,系统会在绘制时自动调整所有绝对的像素坐标和像素尺寸值。这样做是为了确保用像素定义的屏幕元素仍以与可在基准像素密度 (mdpi) 下显示时大致相同的物理尺寸显示。系统会对应用透明地处理这种缩放,并向应用报告缩放后的像素尺寸,而不是物理像素尺寸。例如,假设某个设备配备了 480x800 的 WVGA 高密度屏幕,其尺寸与传统 HVGA 屏幕大致相同,但它运行的应用停用了预缩放。在这种情况下,系统会在应用查询屏幕尺寸并报告 320x533(像素密度的近似 mdpi 转换值)时“依赖于”应用。
然后,当应用执行绘制操作时,例如使 (10,10) 到 (100,100) 的矩形失效,系统会通过将坐标缩放到适当的量来转换坐标,实际上会使 (15,15) 到 (150,150) 的区域无效。如果您的应用直接操控缩放后的位图,这种差异可能会导致意外行为,但为了确保最佳的应用性能,这种差异被视为一种合理的权衡。如果遇到这种情况,请参阅将 dp 单位转换为像素单位。
通常情况下,不会停用预缩放。支持多个屏幕的最佳方法是遵循本页介绍的基本技巧。
如果您的应用操控位图或以其他某种方式直接与屏幕上的像素互动,您可能需要执行额外的步骤来支持不同的像素密度。例如,如果您通过计算手指滑过的像素数来响应触摸手势,则需要使用适当的密度无关像素值,而不是实际像素,但您可以在 dp 和 px 值之间转换。
针对所有像素密度测试
请在具有不同像素密度的多台设备上测试您的应用,以便确保界面正确缩放。尽可能在实体设备上进行测试;如果您无法访问具有各种不同像素密度的实体设备,请使用 Android 模拟器。
如果您想在实体设备上进行测试,但不想购买设备,则可以使用 Firebase Test Lab 访问 Google 数据中心的设备。