如何适配Android手机屏幕
Android适配是一个大坑,你可能早有耳闻。但是别人告诉你坑,然后你也说坑,肯定是无法令人信服的。我们做学问,不能光知其然不知所以然,适配问题到底有多坑,为什么坑,以及如何从坑里爬出来,就是我们今天要探讨的话题了。
这还得从Android的开放性说起。不同于iOS,Android的设备厂商可以生产任意屏幕大小的手机、平板和TV,谷歌对此并没有做任何限制。直接后果就是设备越来越多,大大小小的屏幕尺寸也是层出不穷。另一方面,程序员都有一个梦想,就是一套代码走天下,谁都不想把美好青春浪费在应付各种奇葩的屏幕适配上去。所以说程序员一谈适配色变,尤其是Android的适配,简直比产品经理改需求还要痛苦。这里有两张图,你可以看下当前形势多么严峻。
Android设备碎片化情况
Android设备屏幕尺寸情况
也就是说,我们开发一款Android App,就要对成千上万种屏幕尺寸做适配吗?非也。魔高一尺道高一丈,为了能又精准又省事儿的完成适配,这里有不少方法可以用,我们先从几个基本的概念说起。
1、像素(px)和分辨率
我们的显示屏是由一个一个肉眼看不见但是放大镜可以看见的小点点组成的,这些点点就是像素,是物理世界中存在的东西。分辨率是你的显示屏一共有多少像素。我们平时说的分辨率是1920*1080,就是所谓的1080p,意思是显示器上水平方向有1920个像素,垂直方向有1080个像素,乘起来大概是200W个像素。
2、屏幕密度(dpi)
屏幕密度是对角线上每英寸的屏幕包含多少个像素。比如你手里的iPhone 6 plus,对角线有5.5英寸长,分辨率是1920*1080,那么根据勾股定理(还记得吗,小学语文老师讲过的哦),对角线上有2203个像素,屏幕密度就是2203/5.5=400,单位是dpi或者ppi,二者是一个意思。
在此基础上,Google顺便把手机按照屏幕的密度分了几个档次:
现在主流的手机,都可以找到自己所在的屏幕密度档次。比如一般来说720p的手机是xhdpi,1080p的是xxhdpi。还有一种情况,比如同样都是4英寸的480*800和4英寸的960*540,尽管实际算出来的dpi不一样,但是都要归到hdpi这一档,dpi都变成了240,这是Android系统做的一种近似处理,目的是为了简化计算。也就是说,虽然实际上手机的密度有很多种,但是大家会找到自己的近似区间,然后用区间的代表值去做运算。
3、密度无关像素(dp)
dp是一个虚拟的概念,是在程序运行的时候算出来的。怎么理解呢?Android设备那么多,分辨率也那么多,直接学iOS用px做单位肯定不行的。为此Google搞了一个叫dp的东西,换算公式是dp=(dpi/160)*px。也就是说,在密度为160dpi的屏幕上,1px就是1dp。依次类推,在320dpi的屏幕上,1dp就是2px。屏幕密度越大,1个dp对应的px也就越多。
根据前面讲的屏幕密度区间,你可以记住这样一个简单的计算方法:
用dp有什么好处呢?假设我们现在有两台手机,一台是1280*720,320dpi,一台是1920*1080,480dpi。设计师同学给了一个标注是360px,放在第一台手机上正好是屏幕宽度的一半,但是放在第二台的手机上,则只有宽度的1/3了。这显然是不行的。现在设计师改成了180dp,那么根据公式,在320dpi的手机上,180dp=360px是屏幕宽度720px的一半。在480dpi的手机上,180dp=540px也是屏幕宽度1080px的一半。所以你看到了,dp是用来屏蔽手机的像素密度的差异的,相同dp的标注,在不同分辨率的屏幕上,实际大小都是一致的(从这个角度讲,你可以把dp看做是一个类似厘米、英寸这样的绝对的长度单位,大约160dp等于1英寸)。
相应的,在开发的时候,Google提供了一些资源目录,你可以把对应大小的图片放进去。
举个例子,你想展示一张100dp*100dp的图片,那么在mdpi目录下,你需要放100px*100px的原图。在xxxhdpi下,这张图片就得是300px*300px。你的APP在运行的时候,如果需要加载这张图片,系统就会根据当前手机的密度,去相应的资源目录下去找。你可能会问,找不到怎么办呢?比如当前是mdpi的手机,系统发现mdpi下没有这张图,就会去比mdpi更大的目录找,然后进行缩放。实在找不到就去比mdpi更小的目录找,找到之后再拉伸。
那么,设计师在出图的时候,有两种方法可以选。一是按照官方的推荐方法,在上面所有目录下各放置一份同样的图片,根据dp和px的换算关系切成不同的大小,让系统自动去寻找最合适的图片。这种方法成倍的增加设计师的工作量不说,还会增加安装包的体积,用户下载的时候要多耗费流量,可能过不了隔壁产品同学这一关。第二种方法是选一个基准的屏幕密度,比如xhdpi,720p。所有的资源都放在这里,让系统自动去缩放。这种方法呢,对于小屏幕的手机来说,因为要在运行的时候把一张大图缩放成小图,不如直接用小图节省内存。对大屏幕的手机呢,比如你720p的图拿到1080p的手机去显示,肯定会因为缩放而失真。
综合起来的话,我更倾向于第二种方法。具体选择哪种屏幕密度做标准,你可以参考下Google官方的统计。
还有一些准则,有必要交代一下。
1、尽量是用dp,这是最基本的。
2、如果你使用xhdpi(一般是720p)为基准进行标注,注意它的屏幕宽度是360dp(720/2),而对于hdpi及以下的手机,比如480*800,屏幕宽度是480/1.5=320dp。此时如果你标注的长度超出320dp的话,最好换一种方式。
3、尽量用百分比和相对位置。Android的屏幕分辨率、屏幕密度实在太多了,dp也不是万能的。
如何适配iOS屏幕
上回介绍了Android的屏幕适配,很多同学表示看了之后云里雾里的,尤其是dp这个东西,还要进行数学换算,简直是要命了。所以我决定在介绍iOS之前先花点时间再帮大家理解下Android里的dp。
1、痛点是什么?
痛点就是Android的设备碎片化非常严重,要想一一适配,设计师、开发和测试都压力山大。同一套标注,在一台手机上是一个长度,一个样子,放到另一台手机上就变形了(比如前文举的例子,如果用px,会出现不同手机大小不一样的情况)。所以大家迫切需要一套「write once,run anywhere」的通用度量单位,那就是dp了。
2、诉求是什么?
诉求就是要求在不同分辨率,不同屏幕密度上的手机上,同样dp大小的UI元素,看起来是一样大的。所谓「看起来一样大」,实际上就是要求视觉上看到的物理尺寸是差不多的,所以前文说「你可以把dp看做是一个类似厘米、英寸这样的绝对的长度单位,大约160dp等于1英寸」。
比如说,同样都是720p的屏幕,一台Nexus4,4.7寸的,另一台是6寸的华为第一代Mate,从直观上来讲,Mate屏幕会不如Nexus4显示细腻,就是所谓的颗粒感严重。从理论上讲,就是Mate的「屏幕密度」太低了。现在设计师出了一个图标,长为720px,放在两台手机上,都可以占满宽度,但是明显Mate的720px要比Nexus4的720px在尺寸上长不少。显然这是不合要求的。
3、解决方案是什么?
解决方案就是把屏幕密度纳入dp和px的换算公式里。设计师关心的,用户看到的,都是像素,是px,所以dp最后也是要换算成dp的,关键是按照1比几的比例换算。上面的例子里,Mate的屏幕密度低,显示出来就偏大,所以我们就要让这个换算比例变小。比如Nexus4上,1dp=2px,而Mate上,1dp=1.5px。这样设计师标注的同样dp的UI元素,在两个手机上就会一样大了。
这时候回过头来再看这个公式dp=(dpi/160)*px。我们就会得出两个结论。
- 1dp可以换算成若干px,不同机型不一样。
- 1dp到底可以换算成几个px,取决于这款手机的屏幕密度。屏幕密度越大的手机,1dp可以表示的px也就越多。
所以dp和英寸是类似的物理单位。密度越大的手机,1英寸能盛放的像素越多。密度越大的手机,1dp能表示的像素也越多,是不是这个道理?
所以dp才被叫做密度无关像素(density-independent pixel)。因为无论手机的屏幕密度是多少,1dp总是对应着差不多的物理尺寸。
但是,说了这么多其实都是废话,你只需要记住一点就够了:
- mdpi区间的手机,dp=px。
- hdpi区间的手机,1dp=1.5px。
- xhdpi区间的手机,1dp=2px。
- xxhdpi区间的手机,1dp=3px。
然后我们再来看iOS。其实Android要面对的问题,iOS同样存在。iPhone虽然机型不多,但是经过几次更新换代,屏幕也从最早的3GS(320*480)进化到了现在的iPhone6 plus(1920*1080)。屏幕密度越来越大,如果直接用px的话,还是会出现同样px的UI元素,在不同机型上的大小不一样的问题。于是iOS很机智的选择了点(point)这个和dp几乎一模一样的虚拟单位。
- 在3GS上,1point=1px。
- 在iPhone4上,1point=2px。
- 在iPhone5上,1point=2px。
- 在iPhone6上,1point=2px。
- 在iPhone6 plus上,1point=3px。
为什么1point对应的px数逐渐增大呢?参考Android,根本原因还是屏幕密度越来越大了。比如3GS,3.5英寸的320*480,到了iPhone4,就是3.5英寸的640*960。屏幕尺寸没变,分辨率提升了一倍,密度自然就提升了一倍。
相应的,对于图片,iOS的图片资源可以在名字后面加上@2x表示它是2倍的资源,@3x表示他是3倍的资源,例如icon@2x.png,avatar@3x.png。2倍的资源放在2倍的机型上,完美适配,不需要缩放。但是放在1倍的机型上,就要被系统自动压缩,放在3被的机型上,就要被自动拉伸。对比一下,Android则是把不同分辨率的图片放在不同的文件夹下来区分。二者如此惊人一致。
仔细观察上面的iPhone设备表格,有两个机子比较奇葩。
一个是iPhone5。还记得当年人们说的长长长长长下巴吗?iPhone5和iPhone4都是2倍的屏幕,但是iPhone5的屏幕高度却比iPhone4多了176px。如果程序没有适配iPhone5,跑在iPhone5上就会出现黑边。那么如何告诉系统你的程序已经适配了iPhone5呢?很简单,在程序里添加一张APP起始图片,命名为Default-568h@2x.png就行了。当然,你的UI布局也要相应改变,还好系统从iOS6开始提供了一种AutoLayout,可以用来构建兼容不同屏幕尺寸的界面(改天再细讲)。
另一个是iPhone6 plus。我们来算个账先,iPhone5的屏幕密度是326,倍率是2,那么屏幕密度401的iPhone6 plus,怎么算也不应该是3倍,而是2.46倍啊。但是,2.46这个数字太抽象,苹果为了方便大家计算,还是强行用上了3倍的scale。苹果规定,在iPhone6 plus上,开发者用3倍的资源,也就是按照1242*2208的分辨率出图,然后系统会强制缩放成1920*1080,其实效果也差不了多少。
不得了了,连iOS的机型都这么多了,设计师怎么办啊?还是那句话,以不变应万变。上一回讲Android的时候,提到说可以选择一套基准的分辨率进行标注和出图,然后让系统去自动缩放,其实拿到iOS这里也是可行的。比如你选iPhone6的750*1334为基准,然后向上等比缩放1.5倍到接近6p的1225*2001出3倍资源,向下等比缩放来适配3GS-5s的机型。
哦,对了,还要让开发尽量用AutoLayout来排版布局UI元素。
作者:果果 。
微信公众号:给产品经理讲技术(ID:pm_teacher) PMnote