Android最佳实践,适配多种屏幕

声明:以下内容翻译于Android官网 Supporting Multiple Screens章节。如果有表述不合理的地方,欢迎指正。

众所周知Android设备屏幕尺寸和屏幕密度种类繁多。Android系统提供了一个稳定的开发环境方便应用跨设备,并处理大部分的屏幕适配工作。同时,为了优化我们对不同屏幕的界面设计,Android系统还提供了针对指定屏幕尺寸和分辨率的接口方便我们控制应用界面。

虽然系统可以执行缩放和改变尺寸之类的操作,来使应用在不同屏幕上正常运行,我们还是应该针对不同的屏幕优化我们的应用。这样,就可以使我们的目标用户相信我们的应用是为他们的设备而设计的,而不是简单地拉伸界面去适应屏幕,这样才能最大化的提升用户体验。

屏幕适配概述

该小节主要介绍屏幕适配中的术语和概念,总结系统支持的屏幕参数和屏幕适配相关API的概述。

术语和概念

术语/概念解释
屏幕尺寸物理尺寸,屏幕对角线的长度。为简单起见,Android将不同屏幕尺寸分为四组:
small,normal,large和extra-large
屏幕密度屏幕上一块区域内的像素总量;通常指dpi(每英寸像素点数)。屏幕密度越低,屏幕上指
定区域的像素数越少。为了简单起见,Android将屏幕密度分为六组:low,medium,
high,extra-high,extra-extra-high,extra-extra-extra-high
方向从用户角度,屏幕的方向。包括横向和竖向。
分辨率屏幕像素点的总数。适配多种屏幕时,应用并不直接跟分辨率挂钩。而是结合屏幕尺寸和屏幕密度来考虑
密度无关像素(dp)在编写布局文件时使用的虚拟像素单位,与屏幕密度无关,用来表达布局的尺寸和位置。
在160dpi的屏幕上1dp等于1像素,是定义中等密度(medium)屏幕的基准。运行时,
如果有必要系统会根据实际的屏幕密度对dp单位进行转换。转换公式为:px=dp*(dpi/160).
例如,在240dpi的屏幕上,1dp等于1.5个像素。在定义应用界面时应该始终使用dp为单位,这样才能保证界面在不同屏幕上正常显示

支持的屏幕范围

Android 1.6(API 4)开始,Android就开始支持不同屏幕尺寸和屏幕密度,反映出屏幕参数的多样性。

为了简化设计,Android将实际尺寸和屏幕密度进行如下划分。

  • 四种尺寸:小(small),一般(normal),大(large),加大(extra-large)

    注意:从Android 3.2(API 13) 开始,开始基于屏幕宽度进行尺寸管理,所以这个分组被废弃。下面会详细介绍。

  • 六种屏幕密度

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

以上广义的尺寸和屏幕密度划分,是基于normal 尺寸和mdpi屏幕密度。这个基准是基于第一台Android设备,T-Mobile G1,它使用HVGA屏幕(直到Android1.6 都只有这一种屏幕)。

每种广义的尺寸和屏幕密度都代表一定的范围。例如,两个都是normal尺寸的设备他们的实际屏幕尺寸和宽高比可能会略微不同。屏幕密度也是同样的道理。android将这些不同抽象化,所以我们可以根据这些广义的分类进行设计,在必要的时候由系统来处理最终的适配。下图,不同尺寸和屏幕密度划分:

这里写图片描述

图1

在我们进行界面设计时我们需要一个最小的值来进行设计。所以,每个广义的尺寸都跟一个分辨率关联。这个最小的尺寸是以dp为单位,在我们设计布局时也要使用dp单位。

  • xlarge 的屏幕分辨率最小为 960dp x 720dp
  • large 的屏幕分辨率最小为 640dp x 480dp
  • normal 的屏幕分辨率最小为 470dp x 320dp
  • small 的屏幕分辨率最小为 426dp x 320dp

为了优化不同屏幕上我们的应用界面,我们需要为这些广义的尺寸和屏幕密度分别提供可选资源。一般我们需要提供可选的布局文件和图片资源。应用运行时,系统会基于以上分类为应用选择合适的资源。

不过我们并不需要为每一种分类提供资源。Android系统有强大的兼容性,能够优雅的改变资源尺寸,从而将应用完美的展现在屏幕上。

做到与屏幕密度无关

我们的应用如果能做到不受屏幕的物理尺寸和屏幕密度的影响,那我们的应用便做到屏幕密度无关。

维持密度无关非常重要,否则界面组件在低分辨率屏幕上显示的大,在高分辨率的屏幕上显示的小。这样就会使应用的使用受到影响。下面两图显示了应用在是否做到密度无关时不同的显示效果。

这里写图片描述
图2 未适配不同的屏幕密度。
这里写图片描述
图3 适配了不同的屏幕密度

Android系统从以下两点帮助应用做到屏幕密度无关。

  • 根据屏幕密度对dp单位进行缩放
  • 在需要时根据当前屏幕密度对图片进行缩放

图2中,TextView和bitmap使用px为单位,所以视图在低dpi的屏幕上看起来大,高dpi的屏幕上看起来小。虽然看起来屏幕的实际尺寸一样,高dpi的屏幕单位尺寸的像素点更多。图3中布局使用dp单位。因为中等dpi的屏幕上1dp=1px,所以图2图3 mdpi的屏幕看起来的效果一样,不过在低分辨率和高分辨率的屏幕上,系统对dp进行了适当的缩放来适应屏幕。

多数情况,我们可以统一使用dp单位或者使用“wrap_content”属性来保证应用屏幕密度无关。

不过图片的缩放会导致图片模糊和像素化,从上面的截图可以看出来。为了避免这些缺点,我们应该提供为不同屏幕密度提供可选的图片资源。

如何支持不同的屏幕

  • 在menifest文件中显式声明应用支持的的屏幕尺寸
    通过声明,可以保证只有支持的屏幕尺寸的设备能够下载我们的应用。
    使用<supports-screens> 来声明
    例如下面的声明可以使应用只有平板能下载
    <supports-screens android:smallScreens="false"
                      android:normalScreens="false"
                      android:largeScreens="true"
                      android:xlargeScreens="true"
                      android:requiresSmallestWidthDp="600" />
  • 为不同尺寸的屏幕提供不同的布局文件

    Android会缩放布局文件来适应屏幕,大部分情况,应用都能正常运行。不过有些情况,界面会看起来有些问题,需要根据屏幕进行调整。例如,我们要合理调整组件的位置和大小以便更好的利用大屏幕的提供的空间,小屏幕也是。

    我们可以配置限定符来为不同屏幕尺寸指定布局和资源。例如,为extra-large屏幕准备的布局文件应该放在layout-xlarge/文件夹下。

    从Android 3.2(API 13) 开始,上面的尺寸分组被废弃。我们应该使用sw<N>dp 限定符来定义布局资源需要的最小宽度。例如,平板的多面板布局需要屏幕宽度至少600dp,我们应该把布局文件放在layout-sw600dp/ 目录下。

  • 为不同屏幕密度提供不同图片资源。

    Android默认会缩放我们的图片资源(png,jpg,gif)和9-patch图,这样他们就能在不同屏幕上更好的显示。如果我们的应用只为中等屏幕密度提供了图片资源,系统会对资源放大来适配高屏幕密度的屏幕和将资源缩小来适配低屏幕密度的屏幕。
    不同屏幕密度对应的限定符如下:

    • ldpi–low
    • mdpi–medium
    • hdpi–high
    • xdpi–extra-high
    • xxhdpi–extra-extra-high
    • xxxhdpi– extra-extra-extra-high

    所以为hdpi的屏幕准备的图片应该放在drawable-hdpi/目录下。

    注,如果启动图标在当前设备上显示的会比在xxhpdi设备上大,我们才需要使用mipmap-xxxhdpi 限定符来放置资源。一般情况不需要提供xxxhdpi的资源。

    有些设备会将启动图标放大25%,所以如果我们提供的最高分辨率的图标是xxhdpi,系统的缩放进程会使启动图标看起来模糊。这时就需要在mipmap-xxxhdpi目录下提供一个更高分辨率的图标。不过我们一般只为启动图标提供xxxhdpi限定符的资源,UI组件不使用xxxhdpi限定符。

    注意,将启动图标放在res/mipmap-[density]/文件夹下,而不要放在res/drawable-[density]目录下。因为Android系统在安装应用时不管设备的屏幕分辨率是多少,都会保持在这些文件夹(如mipmap-xxxdpi)下的资源。这样做是为了让登录器(launcher apps)选择最佳分辨率的图标显示在屏幕主页上。

    运行时,系统通过以下过程使应用尽可能的保持最好的显示状态。

    1. 使用合适的资源。

    2. 如果没有找到合适的资源,系统会将默认资源缩放后使用,来适配屏幕。
      那些没有标记限定符的资源便是默认资源。比如,drawable/目录下的资源。系统认为默认资源是为了mdpi屏幕密度和normal屏幕尺寸的设备设计的,所以系统会他们进行缩放来满足其他的屏幕需求。不过如果系统没有找到指定限定符的资源时,也不一定会使用默认资源。例如,如果没有找到ldpi的资源,系统会优先对hdpi版本的资源进行缩小。那是因为将hdpi资源缩小到lldpi需要要乘以0.5,而将mdpi资源缩小到ldpi需要乘以0.75,系统会更倾向于前者来减少失真。

使用配置限定符

Android支持配置限定符,方便我们控制系统根据设备屏幕特性选择资源。配置限定符是一个可以添加在资源文件夹后的字符串。

使用配置限定符
1. 在res/下创建一个文件夹,按<resources_name>-<qualifier>格式命名.
- <resources_name> 是指标准资源名(如drawable或者layout)
- <qualifier>是下面表1提到的限定符,为不同屏幕指定不同资源。
我们也可以同时使用多个限定符,使用时通过“-”分隔。
2. 将合适的资源保存在这个目录下,资源名必须和默认文件夹下的名称相同。

表1,不同屏幕参数的配置限定符

屏幕特征限定符描述
尺寸smallsmall尺寸的屏幕的资源
normalnormal尺寸的屏幕的资源
largelarge尺寸的屏幕的资源
xlargexlarge尺寸的屏幕的资源
屏幕密度ldpildpi的屏幕(~120dpi)使用的资源
mdpimdpi的屏幕(~160dpi)使用的资源
hdpihdpi的屏幕(~240dpi)使用的资源
xhdpixhdpi的屏幕(~320dpi)使用的资源
xxhdpixxhdpi的屏幕(~480dpi)使用的资源
xxxhdpixxxhdpi的屏幕(~640dpi)使用的资源
nodpi所有屏幕密度均可使用的资源。不管屏幕密度如何,系统都不会对该目录下的资源进行
缩放。所以这些资源是屏幕密度无关的
tvdpi为屏幕密度在mdpi和hdpi中间(大约213dpi)的屏幕提供的资源。
这不是一个屏幕密度的基本分组。主要针对电视使用,大部分应用不需要它。
如果需要为tvdpi准备资源,将mdpi的尺寸乘以1.33。
方向land为横屏时提供的资源
port为竖屏时提供的资源
宽高比long为那种跟normal尺寸的屏幕比起来会明显的高或者宽的屏幕提供资源
notlong跟normal尺寸的屏幕比起来类似,但是略短或略窄的屏幕提供资源

例如,下面的资源文件夹为不同的屏幕尺寸提供不同的布局文件和图片资源。使用mipmap/文件夹放置启动图标。
//布局资源
res/layout/my_layout.xml // layout for normal screen size (“default”)
res/layout-large/my_layout.xml // layout for large screen size
res/layout-xlarge/my_layout.xml // layout for extra-large screen size
res/layout-xlarge-land/my_layout.xml // layout for extra-large in landscape orientation
//图片资源
res/drawable-mdpi/graphic.png // bitmap for medium-density
res/drawable-hdpi/graphic.png // bitmap for high-density
res/drawable-xhdpi/graphic.png // bitmap for extra-high-density
res/drawable-xxhdpi/graphic.png // bitmap for extra-extra-high-density
//图标资源
res/mipmap-mdpi/my_icon.png // launcher icon for medium-density
res/mipmap-hdpi/my_icon.png // launcher icon for high-density
res/mipmap-xhdpi/my_icon.png // launcher icon for extra-high-density
res/mipmap-xxhdpi/my_icon.png // launcher icon for extra-extra-high-density
res/mipmap-xxxhdpi/my_icon.png // launcher icon for extra-extra-extra-high-density

在运行时,Android使用某种逻辑来决定最佳资源,也就是说我们使用的限定符并不需要完全符合当前的屏幕参数。尤其是当我们依据屏幕尺寸来选择时,如果没有符合的资源,系统会选择尺寸小一些的屏幕的资源(例如,large-size的设备会使用normal-size的资源)。不过,如果唯一可选的资源仍比当前屏幕大,系统不会使用它,然后系统会因为没有合适的资源而崩溃(例如,如果所有的布局文件都标记了xlarge限定符,但是设备是normal尺寸)。

译者注:尤其注意这里是指依据屏幕尺寸进行抉择,而非屏幕密度。所以如果只有某种屏幕密度的图片资源时不会引起程序崩溃,但是如果只有某种尺寸限定符的布局(layout)文件时,会导致程序崩溃。

要点,如果我们有一些不需要系统进行缩放的资源,我们应该把它们放在带有nodpi限定符文件夹下。这些资源会被系统认定为密度不可知的就不会缩放它们。

设计可选布局和图片

我们应该根据应用的需求来设计可选资源。通常,我们应该使用尺寸和方向限定符来提供可选的布局文件,使用屏幕密度限定符来提供可选的图片资源。

下面分别介绍如何使用限定符在提供可选资源。

可选的布局

总的来讲,当我们在不同屏幕上测试我们的应用的时候我们就会知道应用是否需要可选布局资源。例如:

  • 在小屏幕上,布局不能很好的适配屏幕。例如,一行按钮超出了屏幕的宽度。这时需要调整调整按钮的尺寸和位置为小屏幕的设备提供布局。

  • 在超大屏上,如果应用没有充分利用屏幕或者有明显的拉伸,这时需要为超大屏提供布局。也许应用工作正常,不过用户体验更重要。

  • 测试横竖屏时,发现原本在屏幕底部的组件跑到了屏幕的右边。

总的来讲,我们要保证应用的布局做到以下几点:

  • 在小屏幕上适配很好
  • 在大屏幕上充分利用屏幕空间
  • 充分适配横竖屏

如果界面使用的位图需要适应屏幕的缩放,我们应该使用9-patch图。9-patch图本质上是PNG格式的文件,然后指定可拉伸的二维区域。当需要缩放View使用的位图时,系统拉伸9-patch图的指定区域。这样,我们就不需要为不同的屏幕尺寸提供不同的图片,因为9-patch图可以自适应尺寸。不过,对于不同的屏幕密度我们还是要提供不同的而9-patch图片。

可选的图片

几乎所有的应用都应该为不同的屏幕密度提供可选的图片资源,因为几乎所有的应有都有启动图标,这个图标在不同屏幕密度的设备上应该看起来良好。同样的道理,如果我们的应用包含其他图片,我们应该为不同的屏幕密度提供不同版本的图片。

注意,我们只需要为不同屏幕密度提供位图和9-patch图,对于使用XML定义的形状,颜色和其他图片资源,只需要拷贝在drawable/目录下即可。

为不同屏幕密度生成可选资源时,我们应该在六种广义的屏幕密度之间遵循3:4:6:8:12:16的缩放比。例如,如果我们有一个为mdpi的屏幕准备的48 x 48像素的图片,其他不同的尺寸应该是:

  • 36x36 (0.75x) for ldpi
  • 48x48 (1.0x baseline) for mdpi
  • 72x72 (1.5x) for hdpi
  • 96x96 (2.0x) for xhdpi
  • 144x144 (3.0x) for xxhdpi
  • 192x192 (4.0x) for xxxhdpi (只适用于启动图标,上文有提到)
    如下图:
    这里写图片描述

声明平板的布局

第一代平板运行Android 3.0系统,当时只需要添加xlarge限定符然后将布局文件放在 res/layout-xlarge/目录下即可声明这是平板的布局。后来为了兼容其他类型的平板和屏幕尺寸,尤其是7寸平板,在Android 3.2时引入了一个新的方式来为其他独立的尺寸指定资源。新的技术依赖于我们的布局需要的尺寸(例如,600dp的宽度),而不再是使我们的布局去满足一个广义的尺寸范围(比如large或者xlarge)。

使用广义尺寸分组来设计7寸平板之所以棘手是因为7寸平板和5寸手持设备分在同一个组。这两种设备在尺寸上接近,但是应用界面和操作尺寸却明显不同,用户交互模式也不一样。因此,7寸和5寸屏幕不应该总是使用同样的布局。为了给这两种屏幕提供不同的布局,Android允许我们依据应用实际需要的宽或者高来指定布局。

例如,当我们为平板设计好一套布局之后,你发现如果屏幕宽度小于600dp,应用就不能很好的运行。这个阈值就成了我们的布局需要的最小尺寸。因此,这时我们可以指定这些布局资源只能被用在至少能为应用提供600dp宽度的时候。

注意,在使用新的API时,这些数值都是dp单位,包括我们布局内部也应使用dp单位,因为我们关心的屏幕尺寸是指系统根据屏幕密度换算后的尺寸。

使用新的尺寸限定符

基于我们的布局尺寸我们可以指定不同的资源配置,如下表2。新的限定符给让我们在支持不同屏幕尺寸时更可控。

通过限定符指定的尺寸并不是真实屏幕尺寸,而是指活动窗体能获得的宽度或者高度。Android系统会为系统界面使用一些屏幕空间(比如屏幕顶部的系统栏和状态栏),所以屏幕上有些空间是无法使用的。因此,我们声明的尺寸特指活动窗体需要的尺寸。需要注意的是,ActionBar也被认为是我们应用窗体的一部分,虽然我们布局中没有声明它。所以我们在布局时要考虑到它。

表2,新的配置限定符(Android 3.2 引入)。

屏幕参数限定符描述
smallestWidthsw<N>dp
例子:
sw600dp
sw720dp
可以认为是屏幕可能的最小宽度。我们可以使用这个限定符来保证,不管屏幕方向如何,我们的应用界面至少有<N>dps的宽度。
例如,如果我们的布局始终需要至少600dp的屏幕尺寸,我们可以使用这个限定符来创建布局资源,放在res/layout-sw600dp/目录下。在屏幕的最小尺寸是600dp时,不管是宽还是高,系统会选择这个资源。屏幕方向改变时,设备的smallestWidth并不改变,它是固定的屏幕尺寸特征。
Available screen widthw<N>dp
例子:
w720dp
w1024dp
指定资源使用的最小屏幕宽度为<N>。设备宽度在屏幕方向改变时会相应的改变,
所以我们的应用界面和使用的宽度也会相应变化。
这通常用来定义是否需要使用多面板布局,因为即使是平板,有时我们也不希望在竖屏时分屏显示。所以,这时我们可以使用这个限定符来指定布局文件,而不是通过尺寸和方向限定符一起限定。
Available screen heighth<N>dp
例子:
h720dp
h1014dp
同上,但不常用


使用这些限定符进行设计看起来会比按尺寸分组要复杂,实际上一旦决定应用的界面需求,这种方式会更简单。再设计界面时,我们要关心的主要事情可能是到底屏幕尺寸多大时,进行手持设备风格和平板的多面板风格间的切换。不过这个确切的点依赖于我们的设计,也许我们的平板布局需要720dp的宽度,也许是600dp就足够了又或者是他们之间的其他值。上表定义的限定符可以使我们更准确的控制布局变化的尺寸。

最佳实践

支持多种屏幕的目的是为了使应用能在Android支持的各种屏幕参数设备中也能正常运行并显示正常。前面的内容主要提供了一些关于Android是如何在不同屏幕参数下适配应用的以及我们要如何在不同屏幕参数下定义我们的应用。接下来给出一些要点和一些使应用在不同屏幕参数下正常缩放的方法。

  1. 制定布局尺寸时使用wrap_content,match_pareant和dp单位。

  2. 不要在代码中使用写死的像素值

    出于性能和简化代码的考虑,Android系统使用像素作为尺寸和坐标的标准单位。那意味着视图的尺寸总是基于当前屏幕的屏幕密度,使用px表示。举个例子,如果myView.getWidth() 返回10说明视图在当前屏幕上的宽度是10个像素。不过在更高屏幕密度的屏幕上可能是15个像素。如果我们在代码中使用像素值操作一个没有根据当前屏幕预缩放的位图时,我们需要使用代码对其进行缩放以满足需求。

  3. 不要使用绝对布局(AbsoluteLayout)

  4. 为不同屏幕密度提供不同图片资源

额外补充几点

资源预缩放

基于当前屏幕密度,系统使用指定限定符下的资源然后将他们不缩放的展示出来。如果没有找到正确的资源,系统会加载默认资源然后对他们进行缩放来满足当前屏幕密度的需求。系统认为默认资源是为mdpi的屏幕设计的,所谓预缩放就是指将一个位图的尺寸针对当前屏幕密度进行适当的缩放。

当你请求一个预缩放资源的尺寸时,系统会返回缩放后的尺寸,例如,一个为mdpi设计的50 x 50px的位图在hdpi的屏幕上会被放大到75 x 75px(在没有hdpi的资源的时候),系统返回的尺寸将会是后者。

有时候我们可能不希望系统对资源预缩放,这时只需将资源放在nodpi限定符的文件夹下。这是系统就会直接使用图片不进行缩放。

dp单位转为px单位

有时候我们需要使用dp单位表示尺寸,然后将他们转为px。设想一个应用,用户滑动手势移动16px,在标准屏幕上用户移动了16px/160dpi,等于1/10英尺(或者2.5mm),而在hdpi(240dpi)的屏幕上用户移动了16px/240,等于1/15英尺(或者1.7mm)。这个距离会更短,所以应用表现的更敏感。

为了解决这个问题,手势的阈值应该使用dp为单位,然后转换为px。例如:

// dp单位的手势阈值
private static final float GESTURE_THRESHOLD_DP = 16.0f;
// 获取屏幕的缩放比
final float scale = getResources().getDisplayMetrics().density;
// 结合缩放比将dp转换为px
mGestureThreshold = (int) (GESTURE_THRESHOLD_DP * scale + 0.5f);

DisplayMetrics.density 字段指依据当前屏幕密度,dp转为px使用的缩放比。在mdpi的屏幕上,它等于1.0;在hdpi屏幕上等于1.5;在xhdpi屏幕上等于2.0;ldpi屏幕上等于0.75。这个数值乘以dp值就能得到像素值(再加上0.5后得到最接近的整数)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值