本文为 Qunar 技术沙龙投稿,版权归原作者所有,未经允许,请勿转载。
原文地址:http://mp.weixin.qq.com/s/wV0vcRQ15dZV_ZORMDJk_A
作者:张博源,去哪儿无线公共 Android 开发,热衷于新技术的钻研和探索。团队 Blog:http://mobnav.qunar.com/
【CSDN 有奖征稿啦】技术之路,共同进步,欢迎投稿、给文章纠错,请发送邮件至[email protected],或加微信 tree-rain-chen。
由于 Android 系统的开放性,任何用户、开发者、OEM 厂商、运营商都可以对 Android 进行定制,于是导致:
据友盟指数显示,统计至 2015 年 12 月,支持 Android 的设备共有 27796 种。当 Android 系统、屏幕尺寸、屏幕密度出现碎片化的时候,就很容易出现同一元素在不同手机上显示不同的问题。
虽然系统为使您的应用适用于不同的屏幕,会进行缩放和大小调整,但您应针对不同的屏幕尺寸和密度优化应用。
这样可以最大程度优化所有设备上的用户体验,用户会认为您的应用实际上是专为他们的设备而设计,而不是简单地拉伸以适应其设备屏幕。
一般描述成屏幕的”宽 x 高”=AxB
含义:屏幕在横向方向(宽度)上有 A 个像素点,在纵向方向(高)有 B 个像素点。
例子:1080x1920,即宽度方向上有 1080 个像素点,在高度方向上有 1920 个像素点
UI 设计师的设计图会以 px 作为统一的计量单位。
假设设备内每英寸有 160 个像素,那么该设备的屏幕像素密度=160dpi
安卓手机对于每类手机屏幕大小都有一个相应的屏幕像素密度:
密度类型 | 代表的分辨率(px) | 屏幕像素密度(dpi) |
---|---|---|
低密度(ldpi) | 240x320 | 120 |
中密度(mdpi) | 320x480 | 160 |
高密度(hdpi) | 480x800 | 240 |
超高密度(xhdpi) | 720x1280 | 320 |
超超高密度(xxhdpi) | 1080x1920 | 480 |
一部手机的分辨率是宽 x 高,屏幕大小是以寸为单位,那么三者的关系是:
场景:假如同样都是画一条长度是屏幕一半的线,如果使用 px 作为计量单位,那么在 480x800 分辨率手机上设置应为 240px;在 320x480 的手机上应设置为 160px,二者设置就不同了;如果使用 dp 为单位,在这两种分辨率下,160dp 都显示为屏幕一半的长度。
因为 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
Android 开发时用此单位设置文字大小,可根据字体大小首选项进行缩放。
推荐使用 12sp、14sp、18sp、22sp 作为字体设置的大小,不推荐使用奇数和小数,容易造成精度的丢失问题;小于 12sp 的字体会太小导致用户看不清。
开发中,我们使用的布局一般有:
由于绝对布局(AbsoluteLayout)适配性极差,所以极少使用。
对于线性布局(Linearlayout)、相对布局(RelativeLayout)和帧布局(FrameLayout)需要根据需求进行选择,但要记住:
布局的子控件之间使用相对位置的方式排列,因为 RelativeLayout 讲究的是相对位置,即使屏幕的大小改变,视图之前的相对位置都不会变化,与屏幕大小无关,灵活性很强
通过多层嵌套 LinearLayout 和组合使用“wrap_content”和“match_parent”已经可以构建出足够复杂的布局。但是 LinearLayout 无法准确地控制子视图之间的位置关系,只能简单的一个挨着一个地排列。
所以,对于屏幕适配来说,使用相对布局(RelativeLayout)将会是更好的解决方案。
最小宽度(Smallest-width)限定符
在 Android 3.2 及之后版本,引入了最小宽度(Smallest-width)限定符
定义:通过指定某个最小宽度(以 dp 为单位)来精确定位屏幕从而加载不同的 UI 资源
你需要为标准 7 英寸平板电脑匹配双面板布局(其最小宽度为 600 dp),在手机(较小的屏幕上)匹配单面板布局
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 系统自动选择的)
本质:使得布局组件自适应屏幕尺寸
使用”wrap_content”、”match_parent”和”weight“来控制视图组件的宽度和高度。
“wrap_content”:相应视图的宽和高就会被设定成所需的最小尺寸以适应视图中的内容
“match_parent”(在 Android API 8 之前叫作”fill_parent”):视图的宽和高延伸至充满整个父布局
“weight”
- 定义:是线性布局(Linelayout)的一个独特比例分配属性
- 作用:使用此属性设置权重,然后按照比例对界面进行空间的分配,公式计算是:控件宽度=控件设置宽度+剩余空间所占百分比宽幅
通过使用”wrap_content”、”match_parent”和”weight”来替代硬编码的方式定义视图大小&位置,你的视图要么仅仅使用了需要的那边一点空间,要么就会充满所有可用的空间,即按需占据空间大小,能让你的布局元素充分适应你的屏幕尺寸。
本质:使得图片资源在不同屏幕密度上显示相同的像素效果
使用场景:一个按钮的背景图片必须能够随着按钮大小的改变而改变。
使用普通的图片将无法实现上述功能,因为运行时会均匀地拉伸或压缩你的图片
- 必须要使用.9.png 后缀名,因为系统就是根据这个来区别 nine-patch 图片和普通的 PNG 图片的;
- 当你需要在一个控件中使用 nine-patch 图片时,如
android:background=”@drawable/button”系统就会根据控件的大小自动地拉伸你想要拉伸的部分
本质:使得布局组件在不同屏幕密度上显示相同的像素效果
做法:使用密度无关像素
由于各种屏幕的像素密度都有所不同,因此相同数量的像素在不同设备上的实际大小也有所差异,这样使用像素(px)定义布局尺寸就会产生问题。
因此,请务必使用密度无关像素 dp 或独立比例像素 sp 单位指定尺寸。
相关概念介绍:
(1)密度无关像素
Android 开发时用 dp 而不是 px 单位设置图片大小,是 Android 特有的单位
场景:假如同样都是画一条长度是屏幕一半的线,如果使用 px 作为计量单位,那么在 480x800 分辨率手机上设置应为 240px;在 320x480 的手机上应设置为 160px,二者设置就不同了;如果使用 dp 为单位,在这两种分辨率下,160dp 都显示为屏幕一半的长度。
密度类型 | 代表的分辨率(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)独立比例像素
Android 开发时用此单位设置文字大小,可根据用户的偏好文字大小/字体大小首选项进行缩放
推荐使用 12sp、14sp、18sp、22sp 作为字体设置的大小,不推荐使用奇数和小数,容易造成精度的丢失问题;小于 12sp 的字体会太小导致用户看不清
所以,为了能够进行不同屏幕像素密度的匹配,我们推荐:
可是,请看以下一种场景:
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 加载资源过程:
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 应该是首选。原因如下:
设计师们一般都会用最新的 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 文件夹里就好,这样大大减少的设计师的工作量!