不同于其他系统,Android最厉害的一点就是系统的适配能力超强,小到手表甚至更小,大到投影甚至更大,再加之Google为物联网未来准备的Android Things,未来Android可能内嵌于身边的方方面面,对于开发者来说,是兴奋点,也是巨大的挑战,毕竟适配工作要考虑充分。而这其中,屏幕适配问题是不得不注意也是每个Android应用开发者必须面对的一道坎。
本文研究屏幕适配的方法以及原理。
写在前面
- 文章为原创, 作者为林帅斌,转载请注明出处:我和我的世界@iDIK.net
- 个人水平有限,难免理解出错,望指出共同研究。
在开始之前,先思考几个问题
- 为什么需要适配屏幕?
- 为什么Android不能自动适配不同屏幕?
- 如果让你来设计,该如何设计?
这些问题和下面讨论的可能无关,这里纯粹是想你先思考一下,不要打我
为什么需要适配屏幕?
这个话题可以扯出一篇论文的长度,而这里就不多说了,总的来说,就是Android设备屏幕差别大,使一个应用具备可用性 不得不 进行屏幕适配。
主要原因有二:
- 屏幕大小相差甚远,固定布局很难适应不同屏幕
- 尽管是屏幕相同,分辨率依然有差别,内容显示忽大忽小
如何适配
方案有三:
- 为每一种条件组合准备一种资源
- 自动适配
- 前两者结合的优势
方案一:为每种条件组合准备一种资源布局
这应该是最先想到,也是效果最好的一个方案。
Android的设计者也为这个方案提供了很多必要的支持,例如提供了一套自动匹配条件的布局选择器,你可以根据不同的屏幕大小、不同的分辨率、不同的屏幕方向、甚至不同的语言种类等等等等各准备一套布局,Android系统会为你精准加载相应的布局用来适配屏幕。
例如,你可以为分辨率是120*180的1.8寸屏幕准备一个布局,里面的一张图片大小设为100*100像素,同时为另外一个分辨率是240*360的1.8寸屏幕准备一个布局,里面的同一张图片大小设为200*200像素。这样,在两台分辨率不一样但大小一致的设备上,我们就几乎获得了相同的体验。
这样无论是什么样的屏幕我们都能完美的匹配它,但,代价惨烈 (请务必注意加粗),非官方数据显示Android碎片化那个严重呀,上一张网络流传已久的图片先:
这是2013年的数据,现在应该有甚之,要徒手写这么多了套资源布局,给你刀🔪,你还是给我个痛快吧。。。
方案二:自动适配方案
针对问题本质,如果想自动适配,考虑以下两点:
- 保证UI内容元素显示正常,保证可用性,即内容自适应
- 布局空间根据屏幕大小自适应,即屏幕自适应
内容自适应
内容自适应指的是在不同设备上,UI内容元素显示的效果应该是一致的。即在分辨率是120*180和1080*1920的设备上看到一样大小的文字、图片。这就需要我们想办法忽略“像素”影响,也就是说:
我们设计UI时其实更关注物理尺寸,而非“像素”
如果关注的是像素会怎么样?
举个栗子说明一下问题的严重性,设想一下,在屏幕一样大,像素分别为360*480和1080*1920的设备A和B上, 如果一张图片是360*480像素的,在设备A中,已经是一整屏幕了,而设备B中,却仅仅是一个角落小方块,这样带来的用户体验肯定是不可保证而且不怎么愉悦的。
所以,设计UI是关注的是,我这个图片是2cm*2cm大小的,只有这个大小,图片内的内容才能正确显示出来,而不管什么像素条件下。
这是基于这样的思想,Android设计者设计了一套尺寸规范:DPI & DP。企图使用这套尺寸规范达到忽略底层像素的效果,实际上,的确很不错。
DPI & DP
关注实际尺寸,忽略物理像素,而每个设备屏幕的大小和像素又没有严谨的对应关系,就算屏幕大小一直,但分辨率也有可能不一致,另一方面,我们一旦决定了内容元素的无力尺寸,其实需要的就是系统自动算出相应的像素,所以,Android设计了一个屏幕密度用来计算解决这个问题。
- 何为DPI, 屏幕密度?
屏幕密度就是 单位长度内的像素数。
在Android中,屏幕密度指的是 一英寸长度内的像素数,每一台设备的屏幕是自带屏幕密度属性,根据这个屏幕密度属性,系统就能轻易算出目标尺寸对应在该屏幕上的像素数。
根据这个方案,我们设计UI的时候,就可以用物理尺寸描述:长1英寸,宽2.2英寸等等等等。然后,系统就能帮我们算出在不同的屏幕上,具体应该用多少像素来绘制这个UI。
这有个问题,使用什么长度尺寸单位比较好?
我们习惯的毫米、厘米、分米和米怎么看?不怎么好,因为屏幕大小习惯以英寸来描述,而英寸和通用长度单位的转换不很轻松:1in = 2.54cm,所以计算起来可能很多不方便。那直接使用英寸来描述如何?可以是可以,但因为一般手机屏幕总共大小才5英寸左右,所以经常说这个图标长0.0027英寸,宽0.0029英寸,自自然一批又一批设计师和开发要改行了。
于是呼,Android设计者又发明了一个新的长度单位:DP。
并 强行 定义转换公式为:$1dp = \frac{1}{160}*1in$。(其实定义为160分之1是根据主流屏幕大小与主流屏幕像素研究出来的,以达到实际使用中基本无需使用小数的疗效)
就这样,UI设计师可以愉快的使用物理长度单位DP,而在屏幕上使用多少像素就交给Android系统自行去比划。
- 系统是如何比划的?
这也是不少人很好奇的一点,即DP如何转换成PX?屏幕密度已知,而DP即长度,二者相乘就可以了?答案就是这样,其中值得注意的,就是单位问题,屏幕密度的单位是 $\frac{px}{in}$, 所以我们要先把长度也转换成in即可。
$$px = dpi * (\frac{1}{160} * dp)$$
这就是很多文章经常提到的一个转换公式。
至此,尺寸自适应的问题似乎已经解决了。
每个屏幕的DPI几乎都可能不同,为了方便处理,Android把主流关注的DPI进行了分级,然后把DPI进行归类,这样,处理的难度就简单了很多,尤其是为DPI准备资源时。缺点也是有的,就是DPI不是一个精准值,算出来的像素可能和真实像素有所差别,导致出来的效果和UI设计效果有一定差异,不过差异在可接受范围,也就无关紧要了。具体请看下面👇章节继续讨论。
图片资源问题
那么我们该如何准备我们的图片资源?
根据DPI与DP的讨论我们可以设计一个简单的方案:针对一个DPI提供资源,并告知系统这是为某某DPI准备的资源,系统就会把图片的像素大小转换成dp,再根据目标屏幕的DPI进行转换,得出最终加载完的图片像素大小。
还是举个栗子,
例如我准备了一张320*480像素的图片,告知系统这是为320dpi准备的照片,这时候要显示在480dpi的手机屏幕上,看最终加载完图片会变成多少像素。
第一步, 获取图片DP值:
$$
宽度(dp) = px ÷ dpi * 160 = 320 ÷ 320 * 160 = 160dp
$$
$$
高度(dp) = px ÷ dpi * 160 = 480 ÷ 320 * 160 = 240dp
$$
第二步, 计算在目标及设备屏幕上为多少像素:
$$
宽度(px) = dpi * (\frac{1}{160} * dp) = 480 * (\frac{1}{160} * 160) = 480px
$$
$$
高度(px) = dpi * (\frac{1}{160} * dp) = 480 * (\frac{1}{160} * 240) = 720px
$$
即在该屏幕上,该图片将会自动被拉伸至480*720像素以达到看上去大小一致,如下图所示:
嗯,这个方案一份资源就可以行走天下,很好很强大,然,还是有一些问题
- 低DPI的资源应用在高DPI的目标屏幕上,图片无可避免会被拉伸,而拉伸图片将导致图片模糊等问题带来不好的体验
高DPI的资源应用在低DPI的目标屏幕上,TODO
这样看来一份资源还是有点勉强,那为每一种DPI都准备一份资源如何?显然也是不可能的,所以Android设计者把主流屏幕归纳为ldpi、mdpi、hdpi、xhdpi、xxhdp等几个分级,相对应的具体数值如下表,(随着高分辨率屏的发展,现在已经出现了xxxhdpi,不过此处暂不关注):
DPI级别 | 代表分辨率(px) | DPI |
---|---|---|
ldpi | 240*320 | 120 |
mdpi | 320*480 | 160 |
hdpi | 480*720 | 240 |
xhdpi | 720*1280 | 320 |
xxhdpi | 1080*1920 | 480 |
根据这份表格提供相应DPI的资源即可,其余的交给系统来命中以及缩放。那如果相应的DPI找不到该资源呢?Android有一套自动匹配规则,会自动寻找最优匹配资源,不过对开发者完全透明,而且随着SDK版本规则可能会更新,所以本文章也暂时不研究。
屏幕自适应
屏幕自适应很容易理解,也很容易解决:
大屏幕显示10条数据,小屏幕只显示3条数据。
基于这样的思想,让布局来自动处理不同屏幕的差异即可,而Android系统设计者也的确是这样做的,系统默认提供的布局控件几乎都是支持自适应的,不支持的如AbsoluteLayout等也被标识为不推荐使用。
此外,在布局时,Android也提供了“MATCH_PARENT”、“WRAP_CONTENT”等尺寸属性自适应屏幕、内容大小。
做到这一点,就解决了第一个问题,应用就具备了基本的自适应能力。
小结一下
做好了内容自适应、屏幕自适应,我们的应用在不考虑更好用户体验这个前提下,可以满足大多数设备的自适应需求,而且是自动化的,收益高,我们平时开发应用采取得最多的就是这个方案。
但缺点也是有的,主要体现在:
- 不同屏幕使用同一套UI,虽然内容显示没问题,但交互体验可能不够好,栗子请参考方案三。
- 在局部屏幕上,即使经过处理可能还是显示有问题,问题主要集中在极小屏幕上。
方案三:结合前两者优势
思考一个栗子,新闻客户端,在手机上使用时,我们的屏幕一般较小,所以经常会采取两个页面设计,一个页面为新闻列表,点击任意一条新闻,即可进入新闻详情页面进一步查看:
这个体验不错,但当我们这个应用使用在平板上,根据方案二,我们的内容和布局都会自适应,其中,内容大小不变,布局会被拉伸以容纳更多内容,此时,平板就犹如一台大屏手机,但其实在平板上看新闻,因为一个页面足够大,如果把一个页面分为左右两边,左边显示新闻列表,右边显示新闻详情,点击左边的新闻列表,相应的新闻详情就会显示在右边,不用进行页面跳转,这样的体验很可能更好,而且布局更丰富更美观。
这个时候,我们就可以采取方案一,为平板专门准备一套如上所说的布局:
就是这样,我们综合了方案一和方案二的优点,使我们应用既能自动适配大多数屏幕,又能为个别屏幕提供更好的用户体验,这是我们在实际开发中应该积极考虑的。
总结
关于屏幕适配,Android系统设计者给我设计了完整的一套方案,也为我们提供了必要的技术支持,但还是要根据应用本身的需求来进行自定义具体的适配方案,知晓方案二中的原理能让我们更好的了解自适配的事情,而方案一可以做出更好的用户体验,希望大家结合两方案,也就是方案三,做出自适配性更强、用户体验更好的应用。
附
一些文中没有提到,但好像相关的问题:
.9图是怎么回事?
//todo为什么不效仿Web前端布局方案?
//todo出了上面提到的这两个方案,有没有其他方案?
//todo