赞
踩
本部分提供了具有特定屏幕配置(由屏幕尺寸和密度定义)的设备的相对数量数据。为了简化针对不同屏幕配置设计界面的过程,Android 将实际屏幕尺寸和密度的范围划分为多个区间(如下表所示)。
ldpi | mdpi | tvdpi | hdpi | xhdpi | xxhdpi | Total | |
---|---|---|---|---|---|---|---|
Small | 0.1% | 0.1% | 0.2% | ||||
Normal | 0.4% | 0.3% | 17.0% | 41.1% | 25.9% | 84.7% | |
Large | 1.8% | 2.0% | 0.7% | 2.6% | 2.1% | 9.2% | |
Xlarge | 3.5% | 1.9% | 0.5% | 5.9% | |||
Total | 0.1% | 5.7% | 2.3% | 19.6% | 44.3% | 28.0% |
运行 Android 的设备多种多样,它们有着不同的屏幕尺寸和像素密度。 尽管系统可通过基本的缩放和调整大小功能使界面适应不同屏幕,但您应做出进一步优化,以确保界面能够在各类屏幕上美观地呈现。
Android 设备的形状和尺寸多种多样,因此应用的布局需要十分灵活。也就是说,布局应该从容应对不同的屏幕尺寸和方向,而不是为布局定义刚性尺寸,假定屏幕尺寸和宽高比是一定的。
通过支持尽可能多的屏幕,您的应用可以在各种不同设备上运行,这样您就可以使用单个 APK 将其提供给最多的用户。此外,如果能够使您的应用灵活适应不同的屏幕尺寸,可确保您的应用可以处理设备上的窗口配置更改,例如,当用户启用多窗口模式时发生的窗口配置更改。
本页将向您介绍如何采用以下技巧支持不同的屏幕尺寸:
但是,请注意,适应不同的屏幕尺寸并不一定会使您的应用与所有 Android 设备类型兼容。您应该采取其他措施支持 Android Wear、Android TV、Android Auto 和 Chrome 操作系统设备。
无论您首先要支持什么硬件配置文件,都需要创建一个能够灵活应对屏幕尺寸变化(即便是微小变化)的布局。
如需创建适用于不同屏幕尺寸的自适应布局,最佳做法是将 ConstraintLayout 用作界面中的基本布局。使用 ConstraintLayout,您可以根据布局中视图之间的空间关系指定每个视图的位置和大小。通过这种方式,当屏幕尺寸改变时,所有视图都可以一起移动和拉伸。
如需使用 ConstraintLayout 构建布局,最简单的方法是使用 Android Studio 中的布局编辑器。借助该工具,您可以将新视图拖动到布局中,将其约束条件附加到父视图和其他同级视图以及修改视图的属性,完全不必手动修改任何 XML。
但是,ConstraintLayout
并不能解决所有布局场景(特别是动态加载的列表,对于此类布局,应使用 RecyclerView),但无论您使用何种布局,都应该避免对布局尺寸进行硬编码(请参见下一部分)。
为了确保布局能够灵活地适应不同的屏幕尺寸,您应该对大多数视图组件的宽度和高度使用 "wrap_content"
和 "match_parent"
,而不是硬编码的尺寸。
"wrap_content"
指示视图将其尺寸设为适配该视图中相应内容所需的尺寸。
"match_parent"
使视图在父视图中尽可能地展开。
- <TextView
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:text="@string/lorem_ipsum" />
-
虽然此视图的实际布局取决于其父视图和任何同级视图中的其他属性,但是此 TextView
想要将其宽度设为填充所有可用空间 (match_parent
),并将其高度设为正好是文本长度所需的空间 (wrap_content
)。这样可以使此视图适应不同的屏幕尺寸和不同的文本长度。
如果您使用的是 LinearLayout
,则也可以按布局权重展开子视图,以便每个视图按自身权重值所占的比例填充剩余的空间。但是,在嵌套的 LinearLayout
中使用权重将要求系统执行多次布局遍历以确定每个视图的尺寸,这会降低界面性能。幸运的是,ConstraintLayout
几乎能够构建 LinearLayout
所能构建的所有布局,而不会影响性能,因此您应该尝试将布局转换为 ConstraintLayout。然后,即可使用约束链来定义加权布局。
注意:使用 ConstraintLayout 时,不得使用
match_parent
。而应将尺寸设为0dp
以启用一种称为“匹配约束”的特殊行为,该行为通常与match_parent
的预期行为相同。如需了解详情,请参阅如何调整 ConstraintLayout 中的视图尺寸。
虽然您的布局应始终通过拉伸其视图内部和周围的空间来应对不同的屏幕尺寸,但这可能无法针对每种屏幕尺寸提供最佳用户体验。例如,您为手机设计的界面或许无法在平板电脑上提供良好的体验。因此,您的应用还应提供备用布局资源,以针对特定屏幕尺寸优化界面设计。
您可以通过创建额外的 res/layout/
目录提供特定于屏幕的布局(针对需要不同布局的每种屏幕配置创建一个目录),然后将屏幕配置限定符附加到 layout
目录名称(例如,对于可用宽度为 600dp 的屏幕,附加限定符为 layout-w600dp
)。
这些配置限定符表示应用界面可用的可见屏幕空间。从应用中选择布局时,系统会考虑所有系统装饰(例如导航栏)和窗口配置更改(例如,当用户启用多窗口模式时)。
如需在 Android Studio(使用 3.0 或更高版本)中创建备用布局,请按以下步骤操作:
此时系统会在相应的布局目录中创建一个重复的布局文件,以便您可以开始自定义该屏幕变体的布局。
使用“最小宽度”屏幕尺寸限定符,您可以为具有最小宽度(以密度无关像素 dp 或 dip 为度量单位)的屏幕提供备用布局。
通过将屏幕尺寸描述为密度无关像素的度量值,Android 允许您创建专为非常具体的屏幕尺寸而设计的布局,同时让您不必对不同的像素密度有任何担心。
例如,您可以创建一个名为 main_activity
且针对手机和平板电脑进行了优化的布局,方法是在目录中创建该文件的不同版本,如下所示:
- res/layout/main_activity.xml # For handsets (smaller than 600dp available width)
- res/layout-sw600dp/main_activity.xml # For 7” tablets (600dp wide and bigger)
最小宽度限定符指定屏幕两侧的最小尺寸,而不考虑设备当前的屏幕方向,因此这是一种指定布局可用的整体屏幕尺寸的简单方法。
下面是其他最小宽度值与典型屏幕尺寸的对应关系:
请记住,最小宽度限定符的所有数值都是密度无关像素,因为重要的是系统考虑像素密度(而不是原始像素分辨率)之后可用的屏幕空间量。
注意:您使用这些限定符指定的尺寸不是实际屏幕尺寸,而是 Activity 窗口可用的宽度或高度(以 dp 为单位)。Android 系统可能会将部分屏幕用于系统界面(如屏幕底部的系统栏或顶部的状态栏),因此部分屏幕可能不可供您的布局使用。如果您的应用在多窗口模式下使用,则它只能使用该窗口的尺寸。对该窗口进行大小调整时,它会使用新窗口尺寸触发配置更改,以便系统可以选择适当的布局文件。因此,在声明尺寸时,您应具体说明 Activity 需要的尺寸。在声明为布局提供的空间时,系统会考虑系统界面使用的所有空间。
使用可用宽度限定符
您可能希望根据当前可用的宽度或高度来更改布局,而不是根据屏幕的最小宽度来更改布局。例如,如果您有一个双窗格布局,您可能希望在屏幕宽度至少为 600dp 时使用该布局,但屏幕宽度可能会根据设备的屏幕方向是横向还是纵向而发生变化。在这种情况下,您应使用“可用宽度”限定符,如下所示:
- res/layout/main_activity.xml # For handsets (smaller than 600dp available width)
- res/layout-w600dp/main_activity.xml # For 7” tablets or any screen with 600dp
- # available width (possibly landscape handsets)
如果您关心可用高度,则可以使用“可用高度”限定符来执行相同的操作。例如,对于屏幕高度至少为 600dp 的屏幕,请使用限定符 layout-h600dp
。
虽然您可能只需将“最小宽度”和“可用宽度”限定符结合使用即可支持所有尺寸变化,但是您可能还希望当用户在纵向与横向之间切换屏幕方向时改变用户体验。
为此,您可以将 port
或 land
限定符添加到资源目录名称中。只需确保这些限定符在其他尺寸限定符后面即可。例如:
- res/layout/main_activity.xml # For handsets
- res/layout-land/main_activity.xml # For handsets in landscape
- res/layout-sw600dp/main_activity.xml # For 7” tablets
- res/layout-sw600dp-land/main_activity.xml # For 7” tablets in landscape
在针对多种屏幕尺寸设计应用时,您希望确保不会在 Activity 之间不必要地重复界面行为。因此,您应该使用 Fragment 将界面逻辑提取到单独的组件中。然后,您可以组合 Fragment 以便在大屏幕设备上运行时创建多窗格布局,或者在手机上运行时将 Fragment 放置在单独的 Activity 中。
例如,平板电脑上的一款新闻应用可能在左侧显示报道列表,而在右侧显示一篇完整的报道。在左侧选择一篇报道时,会更新右侧的报道视图。但是,在手机上,这两个组件应显示在单独的屏幕上。从列表中选择一篇报道时,会改变整个屏幕以显示这篇报道。
如果您的应用支持 Android 3.1(API 级别 12)或更低版本,则除上面的最小/可用宽度限定符之外,您还需要使用旧尺寸限定符。
在上面的示例中,如果要在较大的设备上显示双窗格布局,那么您需要使用“large”配置限定符来支持 3.1 及更低版本。因此,要在这些旧版本上实现此类布局,您可能需要创建以下文件:
- res/layout/main_activity.xml # For handsets (smaller than 640dp x 480dp)
- res/layout-large/main_activity.xml # For small tablets (640dp x 480dp and bigger)
- res/layout-xlarge/main_activity.xml # For large tablets (960dp x 720dp and bigger)
如果同时支持低于和高于 Android 3.2 的版本,您必须同时对布局使用最小宽度限定符和“large”限定符。因此,您应创建一个名为 res/layout-large/main.xml
的文件,该文件可能与 res/layout-sw600dp/main.xml
完全相同。
为避免同一文件出现这种重复,您可以使用别名文件。例如,您可以定义以下布局:
- res/layout/main.xml # single-pane layout
- res/layout/main_twopanes.xml # two-pane layout
并添加以下两个文件:
res/values-large/layout.xml
: - <resources>
- <item name="main" type="layout">@layout/main_twopanes</item>
- </resources>
res/values-sw600dp/layout.xml
: - <resources>
- <item name="main" type="layout">@layout/main_twopanes</item>
- </resources>
这两个文件的内容完全相同,但它们实际上并未定义布局,而只是将 main
设置为 main_twopanes
的别名。由于这些文件具有 large
和 sw600dp
选择器,因此它们适用于任何 Android 版本的平板电脑和电视(低于 3.2 版本的平板电脑和电视与 large
匹配,高于 3.2 版本者将与 sw600dp
匹配)。
如果您在改变尺寸的视图中将位图用作背景,您会注意到,当视图根据屏幕尺寸或视图中的内容增大或缩小时,Android 会缩放您的图片。这通常会导致明显的模糊或其他缩放失真。解决方案是使用九宫格位图,这种特殊格式的 PNG 文件会指示哪些区域可以拉伸,哪些区域不可以拉伸。
九宫格位图基本上是一种标准的 PNG 文件,但带有额外的 1 像素边框,指示应拉伸哪些像素(并且带有 .9.png
扩展名,而不只是 .png
)。如图 5 中所示,左边缘和上边缘的黑线之间的交点是可以拉伸的位图区域。
或者,您也可以定义内容在视图内应进入的安全区域,方法是以同样的方式在右边缘和下边缘添加线条。
将九宫格作为背景应用于视图时,框架会正确拉伸图片以适应按钮的尺寸。
Android 设备不仅有不同的屏幕尺寸(手机、平板电脑、电视等),而且其屏幕也有不同的像素尺寸。也就是说,有可能一部设备的屏幕为每平方英寸 160 像素,而另一部设备的屏幕在相同的空间内可以容纳 480 像素。如果您不考虑像素密度的这些差异,系统可能会缩放图片(导致图片变模糊),或者图片可能会以完全错误的尺寸显示。
您必须避免的第一个陷阱是使用像素来定义距离或尺寸。使用像素来定义尺寸会带来问题,因为不同的屏幕具有不同的像素密度,所以同样数量的像素在不同的设备上可能对应于不同的物理尺寸。
要在密度不同的屏幕上保留界面的可见尺寸,您必须使用密度无关像素 (dp) 作为度量单位来设计界面。dp 是一个虚拟像素单位,1 dp 约等于中密度屏幕(160dpi;“基准”密度)上的 1 像素。对于其他每个密度,Android 会将此值转换为相应的实际像素数。
例如,考虑图 1 中的两部设备。如果将某个视图定义为“100px”宽,那么它在左侧设备上看起来要大得多。因此,您必须改用“100dp”来确保它在两个屏幕上看起来大小相同。
不过,在定义文本大小时,您应改用可缩放像素 (sp) 作为单位(但切勿将 sp 用于布局尺寸)。默认情况下,sp 单位与 dp 大小相同,但它会根据用户的首选文本大小来调整大小。
例如,当您指定两个视图的间距时,请使用 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)
假设在某一应用中,用户的手指至少移动 16 像素之后,系统才会识别出滚动或滑动手势。在基线屏幕上,用户必须移动 16 pixels / 160 dpi
(等于一英寸的 1/10 或 2.5 毫米),系统才会识别该手势。而在配备高密度显示屏 (240dpi) 的设备上,用户的手指必须至少移动 16 pixels / 240 dpi
,相当于 1 英寸的 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
单位,即可得出当前屏幕的实际像素数。
您可以使用 ViewConfiguration
类来获取 Android 系统常用的距离、速度和时间。例如,可通过 getScaledTouchSlop()
来获取框架用作滚动阈值的距离(以像素为单位):
private static final int GESTURE_THRESHOLD_DP = ViewConfiguration.get(myContext).getScaledTouchSlop();
ViewConfiguration
中以 getScaled
前缀开头的方法确定会返回不管当前屏幕密度为何都会正常显示的像素值。
要在像素密度不同的设备上提供良好的图形质量,您应该以相应的分辨率在应用中提供每个位图的多个版本(针对每个密度级别提供一个版本)。否则,Android 系统必须缩放位图,使其在每个屏幕上占据相同的可见空间,从而导致缩放失真,如模糊。
您的应用中有多个密度级别可供使用。表 1 描述了可用的不同配置限定符以及它们适用的屏幕类型。
密度限定符 | 说明 |
---|---|
ldpi | 适用于低密度 (ldpi) 屏幕 (~ 120dpi) 的资源。 |
mdpi | 适用于中密度 (mdpi) 屏幕 (~ 160dpi) 的资源(这是基准密度)。 |
hdpi | 适用于高密度 (hdpi) 屏幕 (~ 240dpi) 的资源。 |
xhdpi | 适用于加高 (xhdpi) 密度屏幕 (~ 320dpi) 的资源。 |
xxhdpi | 适用于超超高密度 (xxhdpi) 屏幕 (~ 480dpi) 的资源。 |
xxxhdpi | 适用于超超超高密度 (xxxhdpi) 屏幕 (~ 640dpi) 的资源。 |
nodpi | 适用于所有密度的资源。这些是与密度无关的资源。无论当前屏幕的密度是多少,系统都不会缩放以此限定符标记的资源。 |
tvdpi | 适用于密度介于 mdpi 和 hdpi 之间的屏幕(约 213dpi)的资源。这不属于“主要”密度组。它主要用于电视,而大多数应用都不需要它。对于大多数应用而言,提供 mdpi 和 hdpi 资源便已足够,系统将视情况对其进行缩放。如果您发现有必要提供 tvdpi 资源,应按一个系数来确定其大小,即 1.33*mdpi。例如,如果某张图片在 mdpi 屏幕上的大小为 100px x 100px,那么它在 tvdpi 屏幕上的大小应该为 133px x 133px。 |
要针对不同的密度创建备用可绘制位图资源,您应遵循六种主要密度之间的 3:4:6:8:12:16 缩放比。例如,如果您有一个可绘制位图资源,它在中密度屏幕上的大小为 48x48 像素,那么它在其他各种密度的屏幕上的大小应该为:
然后,将生成的图片文件置于 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
配置限定符的目录中。使用此限定符的资源被视为与密度无关,系统不会缩放它们。
与其他所有位图资源一样,对于应用图标,您也需要提供特定于密度的版本。不过,某些应用启动器显示的应用图标会比设备的密度级别所要求的大差不多 25%。
例如,如果设备的密度存储分区为 xxhdpi,而您提供的最大应用图标密度级别为 drawable-xxhdpi
,则启动器应用会放大此图标,这会导致它看起来不太清晰。因此,您应在 mipmap-xxxhdpi
目录中提供一个密度更高的启动器图标,而后启动器便可改用 xxxhdpi 资源。
由于应用图标可能会像这样放大,因此您应将所有应用图标都放在 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
除了创建多个特定于密度的图片版本之外,另一种方法是仅创建一个矢量图形。在借助矢量图形创建图片时,使用 XML 定义路径和颜色,而不是使用像素位图。因此,矢量图形可以缩放到任何尺寸而不会出现缩放失真,不过它们通常最适合图标等插图,而不太适合照片。
矢量图形通常以 SVG(可缩放矢量图形)文件的形式提供,但 Android 不支持此格式,因此您必须将 SVG 文件转换为 Android 的矢量图格式。
您可以在 Android Studio 中使用 Vector Asset Studio 轻松地将 SVG 转换为矢量图,具体步骤如下:
找到要导入的文件并进行任何调整。
图 3. 使用 Android Studio 导入 SVG 文件
您可能会注意到 Asset Studio 窗口中出现了一些错误,指出文件的某些属性不受矢量图支持。但这不会阻止您导入,只是会忽略不受支持的属性。
点击下一步。
在下一个屏幕上,确认您希望从中查找项目文件的源集,然后点击 Finish。
因为可以对所有像素密度使用一个矢量图,所以此文件位于默认的 drawable 目录中(您不需要使用特定于密度的目录):
- res/
- drawable/
- ic_android_launcher.xml
本部分将详细说明 Android 系统如何在像素密度不同的屏幕上对位图执行缩放,以及您如何进一步控制位图在像素密度不同的屏幕上的绘制方式。除非您的应用操控图形,或者您的应用在像素密度不同的设备上运行时遇到了问题,否则您可以忽略本部分。
为了更好地了解如何在运行时操控图形期间支持多种密度,您应该先了解,系统会通过以下方式帮助确保正确缩放位图:
根据当前屏幕的密度,系统会使用您的应用中特定于密度的任何资源。如果没有针对相应密度的资源可用,系统会加载默认资源,并根据需要将其放大或缩小。系统假定默认资源(即没有配置限定符的目录中的资源)是针对基准像素密度 (mdpi) 设计的,并且会调整这些位图的大小,使其大小适合当前像素密度。
如果您请求预缩放的资源的尺寸,系统将返回表示缩放后尺寸的值。例如,假设针对 mdpi 屏幕设计了一个 50x50 像素的位图,它在 hdpi 屏幕上会放大到 75x75 像素(如果没有针对 hdpi 的备用资源),那么系统会将尺寸报告为 75x75 像素。
在某些情况下,您可能不希望 Android 系统预缩放资源。要避免预缩放,最简单的方法就是将资源放在带有 nodpi
配置限定符的资源目录中。例如:
res/drawable-nodpi/icon.png
当系统使用此文件夹中的 icon.png
位图时,不会根据当前设备密度对其进行缩放。
您可以停用预缩放,具体方法是:将清单中的 android:anyDensity 设置为 "false"
;或者针对 Bitmap
以编程方式将 inScaled
设置为 "false"
。在这种情况下,系统会在绘图时自动缩放所有绝对像素坐标和像素尺寸值。这样做是为了确保按像素定义的屏幕元素的显示尺寸与其在基准像素密度 (mdpi) 屏幕上的物理尺寸大致相同。系统将以对应用透明的方式处理此缩放,并向应用报告缩放后的像素尺寸,而不是物理像素尺寸。
例如,假设某个设备配备 WVGA 高密度屏幕,该屏幕的尺寸为 480x800,与传统 HVGA 屏幕差不多一样大,但在该设备上运行的某个应用停用了预缩放。在这种情况下,当该应用查询屏幕尺寸时,系统会对其“撒谎”,将屏幕尺寸报告为 320x533(针对像素密度为 mdpi 的屏幕转换后得到的近似尺寸)。随后,当该应用执行绘图操作时,例如使 (10,10) 到 (100,100) 的矩形无效,系统会将坐标缩放适当的量来对其进行转换,因而实际上会使 (15,15) 到 (150,150) 的区域无效。如果您的应用直接操控缩放后的位图,此差异可能会导致意外行为,但为了尽可能保持良好的应用性能,这种处理方式被视为一种合理的权衡。如果您遇到这种情况,请阅读将 dp 单位转换为像素单位。
通常,不应停用预缩放。要支持多种屏幕,最好的方法就是遵循本文档中介绍的基本技巧。
如果您的应用操控位图或以其他某种方式直接与屏幕上的像素交互,您可能需要采取其他措施来支持不同的像素密度。例如,如果您通过计算手指滑过的像素数来响应触摸手势,那么需要使用适当的密度无关像素值,而不是实际像素,但您可以轻松地在 dp 和 px 值之间转换。
务必在像素密度不同的多部设备上测试您的应用,这样您就可以确保界面正确缩放。在物理设备上进行测试很简单,但如果您无法访问具有各种不同像素密度的物理设备,那么也可以使用 Android 模拟器。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。