Android适配最核心得问题有两个:一就是适配得效率,把设计图转换成APP界面得过程是否高效;其次就是如何保证实现UI界面在不同尺寸和分辨率的手机中UI的一致性。
在标识尺寸的时候,Android并不推荐我们使用px这个真实像素单位,因为不同的手机之间,分辨率是不同的,比如一个96*96像素的控件在分辨率越来越高的手机上会在整体UI中看起来越来越小。
一、dp直接适配
1、什么是dp
dp指的是设备独立像素,以dp为尺寸单位的控件,在不同分辨率和尺寸的手机上代表了不同的真实像素,比如在分辨率较低的手机中,可能1dp=1px,而在分辨率较高的手机中,可能1dp=2px,这样的话,一个96*96的控件在不同的手机中就能表消除差不多的大小了。
2、如何计算dp
公式:px=dp(dpi/160)系统都是通过这个来判断px和dp的数学关系。其中 dpi 是像素密度,指的是在“系统软件上”指定的单位尺寸的像素数量,它往往是写在系统出厂设置文件的一个固定值。
3、系统软件上
接触手机往往只是听到过ppi的参数,这个在手机屏幕中指的也就是像素密度,但是这个是物理上的概念,它是客观存在的,不会改变。dpi是软件参考了物理像素密度后,人为指定的一个值,这样保证了某一个区间内的物理像素密度在软件上都使用同一个值。这样有利于UI适配。
比如,几部相同分辨率不同尺寸的手机的ppi可能分别是430、440、450,那么在Adnroid系统中,可能dpi会全部指定为480,这样的话,dpi/160就是一个相对固定的数值,这样就能保证相同分辨率下不同尺寸的手机表现一致。而在不同分辨率下,dpi将会不同,比如
更具上面表格,可以发现,720P和1080P的手机,dpi是不同的,这就意味着不同的分辨率中,1dp对应不同数量的px(720P中 1dp=2px,1080P中1dp=3px),这就实现了,我们使用dp来定义一个控件大小的时候,它在不同手机上表现出相应大小的像素值。
dp+自适应布局+weight比例布局可以基本解决不同手机上适配的问题,这也是最原始的Android适配方案。
这种方式存在的问题:第一,这只能保证我们写出来的界面适配绝大部分手机,部分手机仍然需要单独适配,为什么dp只能解决90%的适配问题,因为并不是所有的1080P的手机dpi都是480,比如Goole的pixel2(0920*1080)的dpi是420,也就是说,在pixel2中,1dp=2.625px,这样会导致相同分辨率手机中,一个100dp*100dp的控件,在一般1080P手机上显示的300px,而pixel2中只有262.5px,这样就和实际大小会有所不同。 第二,这种方式无法快速高效的把设计师的设计稿实现到布局代码中,通过dp直接适配,我们只能让UI基本适配不同的手机,但是在设计图和UI代码之间的鸿沟,dp是无法解决的,因为dp不是真实像素。而且设计稿的宽高往往和Android手机真实宽高差别极大。这个鸿沟基本都是通过百分比、估算、设定一个规范值等等。
二、宽高限定符适配
为了高效的实现UI开发,出现了新的适配方案,称之为宽高限定符适配。简单的说就是穷举市面上所有的Android手机的宽高值:
设定一个基准的分辨率,其他分辨率都根据这个基准分辨率来计算,在不同的尺寸文件夹内部,根据该尺寸编写对应的dimens文件。
比如 480X320 为基准分辨率
宽度为320,将任何分辨率的宽度整分为320份,取值为x1-x320
高度为480,将任何分辨率的高度整分为480份,取值为y1-y480
那么对于800*480的分辨率的dimens
x1=(480/320)*1=1.5px x2=(480/320)*2=3px
这个时候,如果我们的UI设计界面使用的就是基准分辨率,那么就可以按照设计稿上的尺寸填写相对应的dimens引用了,而当APP运行在不同分辨率的手机中时,这些系统会根据这些dimens引用去该分辨率的文件夹下面寻找对应的值。这样也就基本达到了适配问题。
致命缺陷:需要精准命中才能适配,比如1920X1080的手机就一定要找到1920X1080的限定字符,否则就只能用统一默认的dimens文件了,而使用默认的尺寸的话,UI就很可能会变形。容错机制很差。
三、UI适配框架
此为一个开源项目,主要是将设计稿的尺寸配置到AndroidManifest中,然后继承开源项目中的Activity,然后布局文件中就可以直接使用像素值了,在APP运行时,框架会自动根据手机的尺寸,然后按照比例伸缩。
解决了容错机制的问题,完美的达到了高效和适配基准的要求。但是因为框架要在运行时会在onMeasure里面做变换,我们自定义的控件可能会被影响或限制,可能有些特定的控件,需要单独适配,这里面可能存在的暗坑时不可预见的。整个适配工作是由框架完成的,而不是系统完成的,一旦使用这个框架,未来一旦遇到很难解决的问题,替换起来就非常麻烦,而且一旦停止维护,后续的升级就只能靠自己了。 所以东西虽好,但是需要慎用。
四、今日头条适配方案
详细说明文章 它是通过修改density值,强行把所有不同尺寸分辨率的手机的宽度dp值改成一个统一的值,这样就能解决所有适配问题。比如,设计稿宽度是360px,那么开发这边就会把目标dp值设置成360dp,在不同的设备中,动态修改density值,从而保证(手机像素宽度)px/density这个值始终是360dp,这样的话,就能保证UI在不同的设备上表现一致了。
这个方案侵入性很低,而且也没有设计到私有API,应该算是一个不错的方案,但是这套方案对老项目是不太友好的,因为修改了系统的density值之后,整个布局的实际尺寸都会发生改变,如果想要在老项目文件中使用,恐怕整个布局文件中的尺寸都可能要重新按照设计稿修改一遍才行。
五、smallestWidth适配
smallestWidth适配 或者教sw限定字符适配,指的是Android会识别屏幕可用高度和宽度的最小尺寸的dp值(其实就是手机的宽度值),然后根据识别到的结果去资源文件中寻找对应限定符的文件夹下的资源文件。
和上面提到的宽高限定符适配原理相同,都是系统通过特定的规则来选择对应的资源文件。
比如,小米5的dpi是480,横向像素是1080px,根据px=dp(dpi/160),横向的dp值是1080/(480/160),也就是360dp,系统就会去寻找是否存在value-sw360dp的文件夹以及对应的资源文件。
smallestWith限定字符适配和宽高限定字符适配最大的区别在于,前者由很好的容错机制,如果没有value-sw360dp文件夹,系统会向下寻找,比如离360dp最近的只有value-sw350dp,那么Android就会选择value-sw350dp文件夹下面的资源文件。
这个方案是以上几个方案中最接近完美的方案。
从开发效率上毫不逊色上述任何一个方案,根据固定的缩放比例,我们基本可以按照UI设计的尺寸不假思索的填写对应的dimens引用。
以375个像素宽度为例,在values-sw360dp文件夹下的dimens文件应该怎么编写呢?
这个文件夹下,手机最小宽度的dp值是360,我们把360dp等分成375等份,每一个设计稿中的像素,大概代表smallestWidth值为360dp的手机中的0.96dp,那么接下来的事情就简单了,计入设计稿上出现一个10px*10px的ImageView,那么我们就可以不假思索的在layout文件中写下对应的尺寸。
<ImageView
android:layout_width="@dimen/qb_px_10"
android:layout_height="@dimen/qb_px_10" />
而这种dimens引用,在不同的values-sw<N>dp文件夹下的数值是不同的,比如values-sw360dp和values-sw400dp:
当系统识别手机的smallestWidth值时,就会自动去寻找和目标数据最接近的资源文件的尺寸。
从稳定性上,它优于上述方案。原生的dp适配可能会碰到pixel 2这种有些特别的手机需要单独适配,但是在smallestWidth适配中,通过计算pixel 2手机的smallestWidth的值时411,我们只需要生成一个values-sw400dp(或者取整生成values-sw410dp也没问题)就能解决问题。
smallestWidth的适配机制由系统保证,我们只需要正对这套规则生成对应的资源文件即可,不会出现难以解决的问题,也根本不会影响我们的业务逻辑代码,而且只要生成的资源文件分布合理,即使对应的smallestWidth值没有找到完全对应的资源文件,它也能向下兼容,寻找最接近的资源文件。
当然smallestWidth适配还是有个小问题的,它时在Android 3.2以后引入的,Goole的本意时用它来适配平板的布局文件(但实际上显然用于dimens适配效果更好),不过目前所有的项目应该最低支持版本应该也是4.0,所以问题其实也不重要了。再有就是dimens文件会导致APK变大,这也是没办法的事情,APK大概增大300KB~800KB左右。
生成dimens文件的过程以及数据计算的方法上面已经赘述了,附一个项目地址用于生成dimens文件, 以供参考: