返回 登录
10

Android 屏幕适配

本文为 Qunar 技术沙龙投稿,版权归原作者所有,未经允许,请勿转载。
原文地址:http://mp.weixin.qq.com/s/wV0vcRQ15dZV_ZORMDJk_A
作者:张博源,去哪儿无线公共 Android 开发,热衷于新技术的钻研和探索。团队 Blog:http://mobnav.qunar.com/
CSDN 有奖征稿啦】技术之路,共同进步,欢迎投稿、给文章纠错,请发送邮件至mobilehub@csdn.net,或加微信 tree-rain-chen。

一、为什么要进行 Android 屏幕适配

由于 Android 系统的开放性,任何用户、开发者、OEM 厂商、运营商都可以对 Android 进行定制,于是导致:

  • Android 系统碎片化: 小米定制的 MIUI、魅族定制的 flyme、华为定制的 EMUI 等等——当然都是基于 Google 原生系统定制的;
  • Android 机型屏幕尺寸碎片化: 5 寸、5.5 寸、6 寸等等;
  • Android 屏幕分辨率碎片化: 320x480、480x800、720x1280、1080x1920。

屏幕尺寸分布图

据友盟指数显示,统计至 2015 年 12 月,支持 Android 的设备共有 27796 种。当 Android 系统、屏幕尺寸、屏幕密度出现碎片化的时候,就很容易出现同一元素在不同手机上显示不同的问题。
虽然系统为使您的应用适用于不同的屏幕,会进行缩放和大小调整,但您应针对不同的屏幕尺寸和密度优化应用。

这样可以最大程度优化所有设备上的用户体验,用户会认为您的应用实际上是专为他们的设备而设计,而不是简单地拉伸以适应其设备屏幕。

二、相关概念

1. 屏幕尺寸

  • 含义:按屏幕对角测量的实际物理尺寸。
  • 为简便起见,Android 将所有实际屏幕尺寸分组为四种通用尺寸:小、 正常、大和超大
  • 单位:英寸(inch),一英寸≈2.54cm

2. 分辨率

  • 含义:手机在横向、纵向上的像素点数总和

一般描述成屏幕的”宽 x 高”=AxB
含义:屏幕在横向方向(宽度)上有 A 个像素点,在纵向方向(高)有 B 个像素点。
例子:1080x1920,即宽度方向上有 1080 个像素点,在高度方向上有 1920 个像素点

  • 单位:px(pixel),1px=1 像素点

UI 设计师的设计图会以 px 作为统一的计量单位。

  • Android 手机常见的分辨率:320x480、480x800、720x1280、1080x1920

3. 屏幕像素密度

  • 含义:每英寸的像素点数
  • 单位:dpi(dots per ich)

假设设备内每英寸有 160 个像素,那么该设备的屏幕像素密度=160dpi

安卓手机对于每类手机屏幕大小都有一个相应的屏幕像素密度:

密度类型 代表的分辨率(px) 屏幕像素密度(dpi)
低密度(ldpi) 240x320 120
中密度(mdpi) 320x480 160
高密度(hdpi) 480x800 240
超高密度(xhdpi) 720x1280 320
超超高密度(xxhdpi) 1080x1920 480

4. 屏幕尺寸、分辨率、像素密度三者关系

一部手机的分辨率是宽 x 高,屏幕大小是以寸为单位,那么三者的关系是:

5. 密度无关像素 (dp)

  • 含义:density-independent pixel,叫 dp 或 dip,与终端上的实际物理像素点无关。
  • 单位:dp,可以保证在不同屏幕像素密度的设备上显示相同的效果
  • Android 开发时用 dp 而不是 px 单位设置图片大小,是 Android 特有的单位

场景:假如同样都是画一条长度是屏幕一半的线,如果使用 px 作为计量单位,那么在 480x800 分辨率手机上设置应为 240px;在 320x480 的手机上应设置为 160px,二者设置就不同了;如果使用 dp 为单位,在这两种分辨率下,160dp 都显示为屏幕一半的长度。

  • dp 与 px 的转换

因为 UI 设计师给你的设计图是以 px 为单位的,Android 开发则是使用 dp 作为单位的,那么我们需要进行转换:

密度类型 代表的分辨率(px) 屏幕像素密度(dpi) 换算(px/dp) 比例
低密度(ldpi) 240x320 120 1dp=0.75px 3
中密度(mdpi) 320x480 160 1dp=1px 4
高密度(hdpi) 480x800 240 1dp=1.5px 6
超高密度(xhdpi) 720x1280 320 1dp=2px 8
超超高密度(xxhdpi) 1080x1920 480 1dp=3px 12

在 Android 中,规定以 160dpi(即屏幕分辨率为 320x480)为基准:1dp=1px

6. 独立比例像素

  • 含义:scale-independent pixel,叫 sp 或 sip
  • 单位:sp

Android 开发时用此单位设置文字大小,可根据字体大小首选项进行缩放。

推荐使用 12sp、14sp、18sp、22sp 作为字体设置的大小,不推荐使用奇数和小数,容易造成精度的丢失问题;小于 12sp 的字体会太小导致用户看不清。

三、如何适配

1. 布局适配

使用相对布局(RelativeLayout),禁用绝对布局(AbsoluteLayout)

开发中,我们使用的布局一般有:

  • 线性布局(Linearlayout)
  • 相对布局(RelativeLayout)
  • 帧布局(FrameLayout)
  • 绝对布局(AbsoluteLayout)

由于绝对布局(AbsoluteLayout)适配性极差,所以极少使用。
对于线性布局(Linearlayout)、相对布局(RelativeLayout)和帧布局(FrameLayout)需要根据需求进行选择,但要记住:

  • 相对布局 RelativeLayout

布局的子控件之间使用相对位置的方式排列,因为 RelativeLayout 讲究的是相对位置,即使屏幕的大小改变,视图之前的相对位置都不会变化,与屏幕大小无关,灵活性很强

  • 线性布局 LinearLayout

通过多层嵌套 LinearLayout 和组合使用“wrap_content”和“match_parent”已经可以构建出足够复杂的布局。但是 LinearLayout 无法准确地控制子视图之间的位置关系,只能简单的一个挨着一个地排列。

所以,对于屏幕适配来说,使用相对布局(RelativeLayout)将会是更好的解决方案。

根据屏幕的配置来加载相应的 UI 布局

最小宽度(Smallest-width)限定符

在 Android 3.2 及之后版本,引入了最小宽度(Smallest-width)限定符

定义:通过指定某个最小宽度(以 dp 为单位)来精确定位屏幕从而加载不同的 UI 资源

  • 使用场景:

你需要为标准 7 英寸平板电脑匹配双面板布局(其最小宽度为 600 dp),在手机(较小的屏幕上)匹配单面板布局

  • 解决方案:您可以使用上文中所述的单面板和双面板这两种布局,但您应使用 sw600dp 指明双面板布局仅适用于最小宽度为 600 dp 的屏幕,而不是使用 large 尺寸限定符。

sw xxxdp,即 small width 的缩写,其不区分方向,即无论是宽度还是高度,只要大于 xxxdp,就采用次此布局

例子:使用了 layout-sw 600dp 的最小宽度限定符,即无论是宽度还是高度,只要大于 600dp,就采用 layout-sw 600dp 目录下的布局

代码展示:

(1)适配手机的单面板(默认)布局:res/layout/main.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:orientation="vertical"
  android:layout_width="match_parent"
  android:layout_height="match_parent">
  <fragment android:id="@+id/headlines"
            android:layout_height="fill_parent"
            android:name="com.example.android.newsreader.HeadlinesFragment"
            android:layout_width="match_parent" />
</LinearLayout>

(2)适配尺寸>7 寸平板的双面板布局:res/layout-sw600dp/main.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="horizontal">
    <fragment android:id="@+id/headlines"
              android:layout_height="fill_parent"
              android:name="com.example.android.newsreader.HeadlinesFragment"
              android:layout_width="400dp"
              android:layout_marginRight="10dp"/>
    <fragment android:id="@+id/article"
              android:layout_height="fill_parent"
              android:name="com.example.android.newsreader.ArticleFragment"
              android:layout_width="fill_parent" />
</LinearLayout>

对于最小宽度≥ 600 dp 的设备,系统会自动加载 layout-sw600dp/main.xml(双面板)布局,否则系统就会选择 layout/main.xml(单面板)布局(这个选择过程是 Android 系统自动选择的)

2. 布局组件适配

本质:使得布局组件自适应屏幕尺寸

  • 做法

使用”wrap_content”、”match_parent”和”weight“来控制视图组件的宽度和高度。
“wrap_content”:相应视图的宽和高就会被设定成所需的最小尺寸以适应视图中的内容
“match_parent”(在 Android API 8 之前叫作”fill_parent”):视图的宽和高延伸至充满整个父布局
“weight”
- 定义:是线性布局(Linelayout)的一个独特比例分配属性
- 作用:使用此属性设置权重,然后按照比例对界面进行空间的分配,公式计算是:控件宽度=控件设置宽度+剩余空间所占百分比宽幅

通过使用”wrap_content”、”match_parent”和”weight”来替代硬编码的方式定义视图大小&位置,你的视图要么仅仅使用了需要的那边一点空间,要么就会充满所有可用的空间,即按需占据空间大小,能让你的布局元素充分适应你的屏幕尺寸。

3. 图片资源适配

本质:使得图片资源在不同屏幕密度上显示相同的像素效果

  • 做法:使用自动拉伸位图:Nine-Patch 的图片类型
    假设需要匹配不同屏幕大小,你的图片资源也必须自动适应各种屏幕尺寸

使用场景:一个按钮的背景图片必须能够随着按钮大小的改变而改变。
使用普通的图片将无法实现上述功能,因为运行时会均匀地拉伸或压缩你的图片

  • 解决方案:使用自动拉伸位图(nine-patch 图片),后缀名是.9.png,它是一种被特殊处理过的 PNG 图片,设计时可以指定图片的拉伸区域和非拉伸区域;使用时,系统就会根据控件的大小自动地拉伸你想要拉伸的部分。
  • 必须要使用.9.png 后缀名,因为系统就是根据这个来区别 nine-patch 图片和普通的 PNG 图片的;
  • 当你需要在一个控件中使用 nine-patch 图片时,如
    android:background=”@drawable/button”系统就会根据控件的大小自动地拉伸你想要拉伸的部分

4. 进行屏幕密度匹配

“布局控件”匹配

本质:使得布局组件在不同屏幕密度上显示相同的像素效果

  • 做法:使用密度无关像素

    由于各种屏幕的像素密度都有所不同,因此相同数量的像素在不同设备上的实际大小也有所差异,这样使用像素(px)定义布局尺寸就会产生问题。
    因此,请务必使用密度无关像素 dp 或独立比例像素 sp 单位指定尺寸。

相关概念介绍:

(1)密度无关像素

  • 含义:density-independent pixel,叫 dp 或 dip,与终端上的实际物理像素点无关。
  • 单位:dp,可以保证在不同屏幕像素密度的设备上显示相同的效果

Android 开发时用 dp 而不是 px 单位设置图片大小,是 Android 特有的单位

场景:假如同样都是画一条长度是屏幕一半的线,如果使用 px 作为计量单位,那么在 480x800 分辨率手机上设置应为 240px;在 320x480 的手机上应设置为 160px,二者设置就不同了;如果使用 dp 为单位,在这两种分辨率下,160dp 都显示为屏幕一半的长度。

  • dp 与 px 的转换
    因为 ui 给你的设计图是以 px 为单位的,Android 开发则是使用 dp 作为单位的,那么该如何转换呢?
密度类型 代表的分辨率(px) 屏幕像素密度(dpi) 换算(px/dp) 比例
低密度(ldpi) 240x320 120 1dp=0.75px 3
中密度(mdpi) 320x480 160 1dp=1px 4
高密度(hdpi) 480x800 240 1dp=1.5px 6
超高密度(xhdpi) 720x1280 320 1dp=2px 8
超超高密度(xxhdpi) 1080x1920 480 1dp=3px 12

在 Android 中,规定以 160dpi(即屏幕分辨率为 320x480)为基准:1dp=1px

T-Mobile G1(第一款 android 手机)的参数属于 mdpi 区域的,以上就是取 160dpi 作为基准的原因。

(2)独立比例像素

  • 含义:scale-independent pixel,叫 sp 或 sip
  • 单位:sp

Android 开发时用此单位设置文字大小,可根据用户的偏好文字大小/字体大小首选项进行缩放

推荐使用 12sp、14sp、18sp、22sp 作为字体设置的大小,不推荐使用奇数和小数,容易造成精度的丢失问题;小于 12sp 的字体会太小导致用户看不清

所以,为了能够进行不同屏幕像素密度的匹配,我们推荐:

  • 使用 dp 来代替 px 作为控件长度的统一度量单位
  • 使用 sp 作为文字的统一度量单位

可是,请看以下一种场景:

Nexus5 的总宽度为 360dp,我们现在在水平方向上放置两个按钮,一个是 150dp 左对齐,另外一个是 200dp 右对齐,那么中间留有 10dp 间隔;但假如同样地设置在 Nexus S(屏幕宽度是 320dp),会发现,两个按钮会重叠,因为 320dp<200+150dp

从上面可以看出,由于 Android 屏幕设备的多样性,如果使用 dp 来作为度量单位,并不是所有的屏幕的宽度都具备相同的 dp 长度

再次明确,屏幕宽度和像素密度没有任何关联关系

所以说,dp 解决了同一数值在不同分辨率中展示相同尺寸大小的问题(即屏幕像素密度匹配问题),但却没有解决设备尺寸大小匹配的问题。(即屏幕尺寸匹配问题)

当然,我们一开始讨论的就是屏幕尺寸匹配问题,使用 match_parent、wrap_content 和 weight,尽可能少用 dp 来指定控件的具体长宽,大部分的情况我们都是可以做到适配的。

“图片资源”匹配

  • 本质:使得图片资源在不同屏幕密度上显示相同的像素效果

  • 做法:提供备用位图(符合屏幕尺寸的图片资源)

    由于 Android 可在各种屏幕密度的设备上运行,因此我们提供的位图资源应该始终可以满足各类密度的要求:

密度类型 代表的分辨率(px) 屏幕像素密度(dpi)
低密度(ldpi) 240x320 120
中密度(mdpi) 320x480 160
高密度(hdpi) 480x800 240
超高密度(xhdpi) 720x1280 320
超超高密度(xxhdpi) 1080x1920 480
  • 步骤 1:根据以下尺寸范围针对各密度生成相应的图片。

    比如说,如果我们为 xhdpi 设备生成了 200x200 px 尺寸的图片,就应该按照相应比例地为 hdpi、mdpi 和 ldpi 设备分别生成 150x150、100x100 和 75x75 尺寸的图片:

即一套分辨率=一套位图资源(这个当然是 Ui 设计师做了)

  • 步骤 2:将生成的图片文件放在 res/ 下的相应子目录中(mdpi、hdpi、xhdpi、xxhdpi),系统就会根据运行您应用的设备的屏幕密度自动选择合适的图片。

  • 步骤 3:通过引用 @drawable/id,系统都能根据相应屏幕的 屏幕密度(dpi)自动选取合适的位图。

如果是.9 图或者是不需要多个分辨率的图片,放在 drawable 文件夹即可

对应分辨率的图片要正确的放在合适的文件夹,否则会造成图片拉伸等问题。

更好的方案解决“图片资源”适配问题

上述方案是常见的一种方案,这固然是一种解决办法,但缺点在于:

  • 每套分辨率出一套图,为美工或者设计增加了许多工作量
  • 对 Android 工程文件的 apk 包变的很大

那么,有没有一种方法:

  • 保证屏幕密度适配
  • 可以最小占用设计资源
  • 使得 apk 包不变大(只使用一套分辨率的图片资源)

方法介绍

先来理解下 Android 加载资源过程:

Android SDK 会根据屏幕密度自动选择对应的资源文件进行渲染加载(自动渲染)。比如说,SDK 检测到你手机的分辨率是 320x480(dpi=160),会优先到 drawable-mdpi 文件夹下找对应的图片资源;但假设你只在 xhpdi 文件夹下有对应的图片资源文件(mdpi 文件夹是空的),那么 SDK 会去 xhpdi 文件夹找到相应的图片资源文件,然后将原有大像素的图片自动缩放成小像素的图片,于是大像素的图片照样可以在小像素分辨率的手机上正常显示。

具体请看:http://blog.csdn.net/xiebudong/article/details/37040263

所以理论上来说只需要提供一种分辨率规格的图片资源就可以了。

那么应该提供哪种分辨率规格呢?

如果只提供 ldpi 规格的图片,对于大分辨率(xdpi、xxdpi)的手机如果把图片放大就会不清晰。所以需要提供一套你需要支持的最大 dpi 分辨率规格的图片资源,这样即使用户的手机分辨率很小,这样图片缩小依然很清晰。

那么这一套最大 dpi 分辨率规格应该是哪种呢?是现在市面手机分辨率最大可达到 1080X1920 的分辨率(dpi=xxdpi=480)吗?

xhdpi 应该是首选。原因如下:

  • xhdpi 分辨率以内的手机需求量最旺盛:目前市面上最普遍的高端机的分辨率还多集中在 720X1080 范围内(xhdpi),所以目前来看 xhpdi 规格的图片资源成为了首选。
  • 节省设计资源&工作量:在现在的 App 开发中(iOS 和 Android 版本),有些设计师为了保持 App 不同版本的体验交互一致,可能会以 iPhone 手机为基础进行设计,包括后期的切图之类的。

设计师们一般都会用最新的 iPhone6 和 iPhone5s(5s 和 5 的尺寸以及分辨率都一样)来做原型设计,所有参数请看下方:

机型 分辨率(px) 屏幕尺寸(inch) 系统密度(dpi)
iPhone 5s 640X1164 4 332
iPhone 6 1334x750 4.7 326
iPhone 6 Plus 1080x1920 5 400

iPhone 主流的屏幕 dpi 约等于 320, 刚好属于 xhdpi,所以选择 xhdpi 作为唯一一套 dpi 图片资源,可以让设计师不用专门为 Android 端切图,直接把 iPhone 的那一套切好的图片资源放入 drawable-xhdpi 文件夹里就好,这样大大减少的设计师的工作量!

参考链接

评论