前言:之前很火的屏幕适配方案不知道大家都去尝试过写进项目中没,应该有一部分人在隔岸观火,大概的原因就是目前并没有遇到能把项目重构的适配问题,另一方面就是有的适配方案还没有很成熟的应用,都不想拿自己的项目去测试。就拿那些github开源库上面的适配方案来说,没有几个人去上面提issues。就在最近我去试了一下今日头条的适配方案,然后。。。哎~~接着往下看吧
一 ppi和dpi这两个单位是什么?有什么关系?
ppi(Pixels per inch) 指每英寸上的物理像素数数目,即 "像素密度“。一般再购买手机的时候都会在参数中看到该设备的ppi数值,ppi数值越大屏幕显像效果越好。不过ppi是物理上的概念,是客观存在的不会改变的值,跟开发中常见的dpi是完全不同的。
dpi(Dots Per Inch)指每英寸有多少个点,最初是用在印刷行业,用来描述每英寸有多少小黑点。dpi被用于Android开发中用来描述屏幕像素密度的单位,是手机出厂就写在系统配置中的一个固定数值,一般是固定不变的,除非你root之后去系统文件中修改这个值,不过手机root有太多的风险,不推荐去root,开发中可以用DisplayMetrics类去获取dpi数值。
ppi和dpi是没有任何关系的。有些文档中ppi 等于 dpi的言论都是瞎扯的,它们之间也没有什么换算关系,还有的文章说 dpi的取值取决于ppi处于哪个dpi的范围,然后取这个范围最大的值,这一点是没有任何的依据,至于dpi的赋值我们也无法得知手机厂商是根据什么去确定的。
ppi的数值我们可以通过以下公式算出,一般的话手机参数里面都能看到ppi的数值,该公式并不适用计算dpi。
dpi不能用上面的公式求出,dpi可以通过DisplayMetrics类的densityDpi属性获取当前手机的dpi数值,该类也可以获取到跟屏幕密度有关的其它属性。一般获取DisplayMetrics类有以下方法:
方式1:
//content:Activity,Content,Application.
DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
方式2:
//getSystemService可以通过 Activity,Content,Application等获取.
DisplayMetrics displayMetrics = new DisplayMetrics();
WindowManager windowManager = (WindowManager)getSystemService(Context.WINDOW_SERVICE);
windowManager.getDefaultDisplay().getMetrics(displayMetrics);
二 为什么dp满足不了现在的屏幕适配(设计图按1080x1920设计)?
为什么强调设计图呢, 因为设计图是UI设计师根据APP的类型以及使用场景精心设计的,同时设计稿直接决定app界面预期的显示效果,决定了每个控件预期的大小,而屏幕适配也是要解决在Android尺寸限制的范围内,按照一套设计图写出的布局要在大部分机型上面显示效果都跟设计图一样。一般的设计师会给一套尺寸,比如1080 X 1920 即 360dp X 640dp 比例 9:16的,或者IOS和Android使用一套设计图(一般都会让Android用IOS的设计稿)。在没有严格要求的话我们只是使用了dp来写布局, 反正现在一直都是,渐渐的发现dp已经逃不过设计师的法眼了(好多机型显示的效果都跟设计图有较多的差异)。
接着看,国内Android手机的ppi数值是厂商定制的,跟手机的硬件相关,ppi数值越大显像效果越好,但ppi只是描述了手机硬件方面的像素密度,并不用于开发中。每种通用的尺寸和密度都涵盖一个实际屏幕尺寸和密度范围。例如, 两部正常屏幕尺寸的设备在手动测量时,实际屏幕尺寸和 高宽比可能略有不同。类似地,对于两台hdpi 屏幕密度的设备,其实际像素密度可能略有不同。 Android 将这些差异抽象概括到应用,使您可以提供为通用尺寸和密度设计的 UI,让系统按需要处理任何最终调整。(有可能Android手机系统出厂设置的dpi数值也会参考该取值范围的)。
ldpi(低)~120dpi
mdpi(中)~160dpi
hdpi(高)~240dpi
xhdpi(超高)~320dpi
xxhdpi(超超高)~480dpi
xxxhdpi(超超超高)~640dpi
小屏幕至少为 426dp x 320dp
正常屏幕至少为 470dp x 320dp
大屏幕至少为 640dp x 480dp
超大屏幕至少为 960dp x 720dp
为了简化屏幕适配,一般机型的dpi的取值会参考上面的范围,但是总会有一些特殊的机型就是不采纳官方的建议。如小米 MIX 2 分辨率 2160x1080 屏幕尺寸 为6, ppi为403 获取到的dpi为440,该分辨率下的手机dpi 大致为480。为什么要强调dpi的数值呢?我想大家都知道我们再布局的尺寸方面都会选择dp,因为dp是会随着分辨率的不同而变化的,一般的关系如下:
dpi 120 : 1dp = 0.75px;
dpi 160 : 1dp = 1px;
dpi 240 : 1dp = 1.5px;
dpi 320 : 1dp = 2px;
dpi 480 : 1dp = 3px;
dpi 640 : 1dp = 4px;
计算公式:
px = density * dp;
dp = px / density;
density = dpi / 160;
根据上面的公式可以看到dpi影响了dp转px的数值,所以可以说dp适配也就是dpi的适配,对于360dp X 640dp的设计稿来说,对应的分辨率为1080 X 1920和1440 X 2560,使用的数值为 1dp = 3px。正常的机型我们使用dp的话基本可以完成适配,但是当碰到分辨率一样dpi不同的手机,比如dpi = 440 1dp = 2.75px 或者 dpi = 420 1dp = 2.625px 的机型的时候,那就懵逼了,如一个Button的宽度为100dp,再dpi = 480的机型中显示的宽度效果为300px,再dpi = 440显示的效果宽度为275px,这样我们的布局就会跟预期的不太一样,这是dp无法适配的。
另外现在主流的是大屏手机,长度方向的像素点一般大于1920px,大致在2040px~2880px之间,但是宽度基本保持再1080px,配置好的手机是1440px,市场90%以上主流手机宽度都是1080px的。如:
华为:
nova 2s ,Mate 10 Pro 等等分辨率是2160X1080 dpi = 480 ;
nova 3 2340 x1080 dpi = 480;
小米:
MIX 2040x1080 dpi = 480, MIX2 2160x1080 dpi = 440 ,
Max 2160X1080 dpi = 480等等;
oppo:
R11s 2160x1080 dpi = 480, R15 2280x1080 dpi = 480,
等等...
手机dpi的大小决定了当前dp转px的倍数关系,目前大部分机型的dpi都是480,也就是说设计图上一个组件的margintop 为100dp = 300px,那么当运行在分辨率为1080X2280的机型中该组件相对于设计图的位置就会偏上,在分辨率为1080x1920的机型中正常,这就会导致一个问题,在大屏手机中正好显示完整的布局会再小屏幕中就会出现控件被遮挡或者控件的高度比不一致,最明显的就是开屏页的logo位置。这也是dp无法解决的适配问题。
个人而言,适配宽度用dp基本能够适配,毕竟那些特殊dpi的机型还是少数,写布局注意点的话就不会出现太明显的适配问题。适配高度就需要使用其他的更有效的适配方式了。
三 宽高限定符,AndroidAutoLayout,smallestWidth,今日头条适配方案怎么取舍?
宽高限定符适配和smallestWidth适配方案大致思想都是一样,smallestWidth比宽高限定符更加的智能可靠。但是这两种方案需要增加好多资源文件,想要适配什么屏幕就要去配置该类型的资源文件,全局适配。这两种适配方案再宽高适配上还是很有效果的。鸿神的AndroidAutoLayout已经停止维护了,我想大家都不会优先考虑这个方案了,这里也不去讨论。今日头条适配方案我想大家都或多或少的了解过,该方案还是比较精简灵活的,可以自己选择以宽度适配还是高度适配,下面是在高度纬度上面的测试数据:
设计图:
360dp X 640dp 分辨率为 1080 X 1920 这里的屏幕高度包括状态栏。
控件高度为103dp 高度/屏幕高度 = 0.1609375.
模拟器 1:
分辨率为 1080 x 2280 .实际是 1080 X 2136 .状态栏高度Wie:72px.
控件高度为103dp 高度/屏幕高度 = 0.1497093. 适配后:0.1609649.
模拟器 2:
分辨率为 1080 x 1920 .实际是 1080 X 1776 .状态栏高度Wie:72px.
控件高度为103dp 高度/屏幕高度 = 0.18133803.适配后:0.16035.
模拟器3:
分辨率为 480 * 800.状态栏高度Wie:36px. 尺寸小于设计图的.
控件高度为103dp 高度/屏幕高度 = 0.19375. 适配后:0.16125.
小米4:
分辨率为 1080 x 1920 .状态栏高度Wie:60px.
控件高度为103dp 高度/屏幕高度 = 0.16612904. 适配后:0.1609375.
小米MIX2:
分辨率为 1080 x 2160 .状态栏高度Wie:66px. 底部虚拟导航键高度为:130px
控件高度为103dp 高度/屏幕高度 = 0.13940887. 适配后:0.16108374.
OPPO R15:
分辨率 1080 x 2280. 尺寸是 6.28 . 状态栏高度为:84px.
控件高度为103dp 高度/屏幕高度 = 0.13552631. 适配后:0.1609649.
华为p20:
分辨率为 1080 x 2240 .状态栏高度Wie:85px.
控件高度为103dp 高度/屏幕高度 = 0.13770053.适配后:0.16087344.
oppo R9s:
分辨率为 1080 x 1920 .状态栏高度Wie:54px.
控件高度为103dp 高度/屏幕高度 = 0.1609375. 适配后:0.1609375.
用今日头条的适配方案后再大屏手机中的高度比基本等于设计图中的高度比,这样在屏幕高度相差很大的真机环境中显示效果会好很多。今日头条适配方案更加的灵活,我们再适配的时候虽然是全局的修改,但是我们可以指定特定的界面上不适配(也就是把设置恢复为默认的设置),这样即使是第三方的界面只要有代码就可以选择适配适配。另外还可以的自由的配置是以宽度为基准还是以高度为基准点去适配,但是两者不能兼得。
四 今日头条适配方案到底可行吗?
那么问题来了,再日常开发中只是适配宽度的话,遇见的需求不多,适配高度确实是遇见不少,然后我再适配高度的时候发现了问题。当我们用今日头条适配方案在高度上去适配大屏手机的话(比如分辨率为1080X2160)那样计算出来的dpi的数值肯定会比原数值高好多。比如小米 MIX2 分辨率为 1080 X 2160 高度适配之后再高度纬度的dpi数值为523 那么就是100dp = 317px,正常情况的dpi为440 100dp = 275px。高度适配之后对宽度方向影响很大的。对下表的数据分析能看出,目前流行机型的宽度定大部分都在1080,高度大于1920的机型居多,再大屏手机里面我们要首选适配高度的问题,先来看下一个简单的适配问题。
需求:开屏页logo展示位置。
设计稿: 1080px X 1920px 360dp X 640dp。
logo: 大小100dp X 100dp 水平居中,marginTop100dp。topMargin / 屏幕高度:0.15635。
测试机型:小米四(1080X1920) vivo x21(1080X2280)。
真机数据未适配前:
未适配前:
小米4:
10-12 10:28:25.146 12746-12746/cn.screen.adaptation E/WANG: getWidth300
10-12 10:28:25.146 12746-12746/cn.screen.adaptation E/WANG: getHeight300
10-12 10:28:25.146 12746-12746/cn.screen.adaptation E/WANG: topMargin / 屏幕高度0.15625
VIVO X21:
10-12 10:31:15.246 23724-23724/cn.screen.adaptation E/WANG: getWidth300
10-12 10:31:15.246 23724-23724/cn.screen.adaptation E/WANG: getHeight300
10-12 10:31:15.246 23724-23724/cn.screen.adaptation E/WANG: topMargin / 屏幕高度0.13157895
我们可以看到小米4手机的topMargin / 屏幕高度跟设计图的一致。VIVO X21就相差很大了。这样显示出来的logo的位置就会跟设计图设计的有很大的差距,这种差距是随着手机竖直分辨率的增大而增大。
真机适配后:
高度适配后:
小米4:
10-12 10:28:25.146 12746-12746/cn.screen.adaptation E/WANG: getWidth300
10-12 10:28:25.146 12746-12746/cn.screen.adaptation E/WANG: getHeight300
10-12 10:28:25.146 12746-12746/cn.screen.adaptation E/WANG: topMargin / 屏幕高度0.15625
VIVO X21:
10-12 10:30:33.760 23502-23502/? E/WANG: getWidth356
10-12 10:30:33.760 23502-23502/? E/WANG: getHeight356
10-12 10:30:33.760 23502-23502/? E/WANG: topMargin / 屏幕高度0.15614036
我们可以明显的看到logo的topMargin / 屏幕高度基本跟设计搞的一致,这样就达到了logo在大多数机型上面显示的效果跟设计稿的一样。但是可以发现logo的宽高都增加了56px,这也是因为适配高度的时候更改了dpi的数值,dpi的数值偏大就会造成全局的dp转px的倍率变大,这样我们的logo的大小和该界面的其它的控件的大小都会有影响。
总结:
屏幕适配任重而道远,我们要针对设计稿,针对界面,针对控件去选择我们的适配方式,技术好并不代表好用,有的时候会反其道而行之。本人还是很喜欢今日头条适配方案的,用注解做起来逼格瞬间提升,想再那个界面适配就在那个界面适配,想取消适配就取消适配,也就一个注解的事。另外还有一点就是,适配方案推出那么多时间也不短了,有几个开发者实战了呢?所谓实践出真理今日头条适配方案坑很多,我们一起慢慢踩~~欢迎大家提出文章里面的错误,大家共同学习!
参考 https://developer.android.google.cn/guide/practices/screens_support
欢迎关注:
我的掘金
我的
我的CSDN
我的Github
注解版今日头条适配方案 (供参考学习)
五 主流机型
注: 以下机型的dpi数值只有一部分得到真机验证,其余存在些许误差望更正,体现在(宽/density)这个数值上。
华为-荣耀系列:
机型 | 分辨率 | ppi | 尺寸 | 宽/density | 高/density |
---|---|---|---|---|---|
华为畅享8 | 1440x720 | 269 | 5.99 | 360dp | 720dp |
华为nova 2 | 1920x1080 | 440 | 5 | 360dp | 640dp |
华为P9 | 1920x1080 | 424 | 5.2 | 360dp | 640dp |
华为Mate 9 | 1920x1080 | 373 | 5.9 | 360dp | 640dp |
华为P10 | 1920x1080 | 432 | 5.1 | 360dp | 640dp |
华为Mate 10 Pro | 2160x1080 | 402 | 6 | 360dp | 720dp |
华为nova 2s | 2160x1080 | 402 | 6 | 360dp | 720dp |
华为畅享8 Plus | 2160x1080 | 407 | 5.93 | 360dp | 720dp |
华为Mate 10 Pro | 2160x1080 | 402 | 6 | 360dp | 720dp |
华为nova 2s | 2160x1080 | 402 | 6 | 360dp | 720dp |
华为P20 Pro | 2240x1080 | 408 | 6.1 | 360dp | 746.7dp |
华为P20 | 2244x1080 | 428 | 5.8 | 360dp | 748dp |
华为nova 3e | 2280x1080 | 432 | 5.84 | 360dp | 760dp |
华为nova 3i | 2340x1080 | 409 | 6.3 | 360dp | 780dp |
华为nova 3 | 2340x1080 | 409 | 6.3 | 360dp | 780dp |
华为Mate 10 | 2560x1440 | 498 | 5.9 | 360dp | 640dp |
华为Mate 20 | 2560x1440 | 482 | 6.1 | 360dp | 640dp |
华为Mate RS保时捷版 | 2880x1440 | 537 | 6 | 360dp | 720dp |
小米:
机型 | 分辨率 | ppi | 尺寸 | 宽/density | 高/density |
---|---|---|---|---|---|
小米红米6 | 1440x720 | 295 | 5.45 | 360dp | 720dp |
小米Max 2 | 1920x1080 | 342 | 6.44 | 360dp | 640dp |
小米5X | 1920x1080 | 401 | 5.5 | 360dp | 640dp |
小米6 | 1920x1080 | 428 | 5.15 | 360dp | 640dp |
小米Max 2 | 1920x1080 | 342 | 6.44 | 360dp | 640dp |
小米MIX | 2040x1080 | 361 | 6.4 | 360dp | 680dp |
小米6X | 2160x1080 | 403 | 6.0 | 360dp | 720dp |
小米MIX 2s | 2160x1080 | 403 | 6.0 | 360dp | 720dp |
小米红米Note 5 | 2160x1080 | 403 | 6.0 | 360dp | 720dp |
小米Max 3 | 2160x1080 | 350 | 6.9 | 360dp | 720dp |
小米MIX 2 | 2160x1080 | 403 | 6.0 | 392.7dp | 785.5dp |
小米8 SE | 2244x1080 | 424 | 5.88 | 360dp | 748dp |
小米8 | 2248x1080 | 402 | 6.21 | 360dp | 749.3dp |
小米8透明探索版 | 2248x1080 | 402 | 6.21 | 360dp | 749.3dp |
小米红米6 Pro | 2280x1080 | 432 | 5.84 | 360dp | 760dp |
OPPO
机型 | 分辨率 | ppi | 尺寸 | 宽/density | 高/density |
---|---|---|---|---|---|
OPPO A57 | 1280x720 | 282 | 5.2 | 360dp | 640dp |
OPPO A83 | 1440x720 | 282 | 5.7 | 360dp | 720dp |
OPPO A5 | 1520x720 | 271 | 6.2 | 360dp | 760dp |
OPPO R9S | 1920X1080 | 401 | 5.5 | 360dp | 640dp |
OPPO R11 | 1920x1080 | 401 | 5.5 | 360dp | 640dp |
OPPO R11 Plus | 1920x1080 | 367 | 6 | 360dp | 640dp |
OPPO R11s | 2160x1080 | 401 | 6.0 | 360dp | 720dp |
OPPO R11s Plus | 2160x1080 | 376 | 6.43 | 360dp | 720dp |
OPPO R15 | 2280x1080 | 402 | 6.28 | 360dp | 760dp |
OPPO A3 | 2280x1080 | 405 | 6.2 | 360dp | 760dp |
OPPO R17 | 2340x1080 | 402 | 6.4 | 360dp | 780dp |
OPPO Find X | 2340x1080 | 401 | 6.42 | 360dp | 780dp |
OPPO R17 Pro | 2340x1080 | 402 | 6.4 | 360dp | 780dp |
VIVO
机型 | 分辨率 | ppi | 尺寸 | 宽/density | 高/density |
---|---|---|---|---|---|
vivo Y71 | 1440x720 | 269 | 6.0 | 360dp | 720dp |
vivo Y83 | 1520x720 | 270 | 6.22 | 360dp | 760dp |
vivo x7Plus | 1920x1080 | 386 | 5.7 | 360dp | 640dp |
vivo X20Plus | 2160x1080 | 376 | 6.43 | 360dp | 720dp |
vivo X20 | 2160x1080 | 401 | 6.0 | 360dp | 720dp |
vivo Y97 | 2280x1080 | 401 | 6.3 | 360dp | 760dp |
vivo X21屏幕指纹版 | 2280x1080 | 402 | 6.28 | 360dp | 760dp |
vivo X21 | 2280x1080 | 402 | 6.28 | 360dp | 760dp |
vivo Z1 | 2280x1080 | 403 | 6.26 | 360dp | 760dp |
vivo Y85 | 2280x1080 | 403 | 6.26 | 360dp | 760dp |
vivo NEX | 2316x1080 | 388 | 6.59 | 360dp | 772dp |
vivo X23 | 2340x1080 | 402 | 6.4 | 360dp | 780dp |
魅族
机型 | 分辨率 | ppi | 尺寸 | 宽/density | 高/density |
---|---|---|---|---|---|
魅族魅蓝S6 | 1440x720 | 282 | 5.7 | 360dp | 720dp |
魅族15 | 1920x1080 | 403 | 5.46 | 360dp | 640dp |
魅族PRO 7 | 1920x1080 | 424 | 5.2 | 360dp | 640dp |
魅族魅蓝Note 6 | 1920x1080 | 401 | 5.5 | 360dp | 640dp |
魅族16th | 2160x1080 | 402 | 6 | 360dp | 720dp |
魅族16th Plus | 2160x1080 | 372 | 6.5 | 360dp | 720dp |
魅族16 X | 2160x1080 | 402 | 6.0 | 360dp | 720dp |
魅族15 Plus | 2560x1440 | 494 | 5.95 | 360dp | 640dp |
魅族PRO 7 Plus | 2560x1440 | 515 | 5.7 | 360dp | 640dp |
锤子
机型 | 分辨率 | ppi | 尺寸 | 宽/density | 高/density |
---|---|---|---|---|---|
锤子科技Smartisan T2 | 1920x1080 | 445 | 4.95 | 360dp | 640dp |
锤子科技坚果Pro | 1920x1080 | 401 | 5.5 | 360dp | 640dp |
锤子科技坚果Pro 2S | 2160x1080 | 402 | 6.0 | 360dp | 720dp |
锤子科技坚果Pro 2 | 2160x1080 | 402 | 6.0 | 360dp | 720dp |
锤子科技坚果R1 | 2240x1080 | 403 | 6.17 | 360dp | 746.7dp |