px = density dp;
density = dpi / 160;
px = dp (dpi / 160);
而dpi是根据屏幕真实的分辨率和尺寸来打算的,每个设备都可能不一样的。
屏幕尺寸、分辨率、像素密度三者关系
常日情形下,一部手机的分辨率是宽x高,屏幕大小因此寸为单位,那么三者的关系是:
举个例子:屏幕分辨率为:19201080,屏幕尺寸为5吋的话,那么dpi为440。
这样会存在什么问题呢?
假设我们UI设计图是按屏幕宽度为360dp来设计的,那么在上述设备上,屏幕宽度实在为1080/(440/160)=392.7dp,也便是屏幕是比设计图要宽的。这种情形下, 纵然利用dp也是无法在不同设备上显示为同样效果的。 同时还存在部分设备屏幕宽度不敷360dp,这时就会导致按360dp宽度来开拓实际显示不全的情形。
而且上述屏幕尺寸、分辨率和像素密度的关系,很多设备并没有按此规则来实现, 因此dpi的值非常乱,没有规律可循,从而导致利用dp适配效果差强人意。
探索新的适配办法梳理需求
首先来梳理下我们的需求,一样平常我们设计图都因此固定的尺寸来设计的。比如以分辨率1920px 1080px来设计,以density为3来标注,也便是屏幕实在是640dp 360dp。如果我们想在所有设备上显示完备同等,实在是不现实的,由于屏幕高宽比不是固定的,16:9、4:3乃至其他宽高比层出不穷,宽高比不同,显示完备同等就不可能了。但是常日下,我们只须要以宽或高一个维度去适配,比如我们Feed是高下滑动的,只须要担保在所有设备中宽的维度上显示同等即可,再比如一个不支持高下滑动的页面,那么须要担保在高这个维度上都显示同等,尤其不能存在某些设备上显示不全的情形。同时考虑到现在基本都因此dp为单位去做的适配,如果新的方案不支持dp,那么迁移本钱也非常高。
因此,总结下大致需求如下:
支持以宽或者高一个维度去适配,保持该维度上和设计图同等;
支持dp和sp单位,掌握迁移本钱到最小。
找兼容打破口
从dp和px的转换公式 :px = dp density
可以看出,如果设计图宽为360dp,想要担保在所有设备打算得出的px值都恰好是屏幕宽度的话,我们只能修正 density 的值。
通过阅读源码,我们可以得知,density 是 DisplayMetrics 中的成员变量,而 DisplayMetrics 实例通过 Resources#getDisplayMetrics 可以得到,而Resouces通过Activity或者Application的Context得到。
先来熟习下 DisplayMetrics 中和适配干系的几个变量:
DisplayMetrics#density 便是上述的density
DisplayMetrics#densityDpi 便是上述的dpi
DisplayMetrics#scaledDensity 字体的缩放因子,正常情形下和density相等,但是调节系统字体大小后会改变这个值
那么是不是所有的dp和px的转换都是通过 DisplayMetrics 中干系的值来打算的呢?
首先来看看布局文件中dp的转换,终极都是调用 TypedValue#applyDimension(int unit, float value, DisplayMetrics metrics) 来进行转换:
这里用到的DisplayMetrics正是从Resources中得到的。
再看看图片的decode,BitmapFactory#decodeResourceStream方法:
可见也是通过 DisplayMetrics 中的值来打算的。
当然还有些其他dp转换的场景,基本都是通过 DisplayMetrics 来打算的,这里不再详述。因此,想要知足上述需求,我们只须要修正 DisplayMetrics 中和 dp 转换干系的变量即可。
终极方案
下面假设设计图宽度是360dp,以宽维度来适配。
那么适配后的 density = 设备真实宽(单位px) / 360,接下来只须要把我们打算好的 density 在系统中修正下即可,代码实现如下:
同时在 Activity#onCreate 方法中调用下。代码比较大略,也没有涉及到系统非公开api的调用,因此理论上不会影响app稳定性。
于是修正后上线灰度测试了一版,稳定性符合预期,没有收到由此带来的crash,但是收到了很多字体过小的反馈:
缘故原由是在上面的适配中,我们忽略了DisplayMetrics#scaledDensity的分外性,将DisplayMetrics#scaledDensity和DisplayMetrics#density设置为同样的值,从而某些用户在系统中修正了字体大小失落效了,但是我们还不能直接用原始的scaledDensity,直接用的话可能导致某些笔墨超过显示区域,因此我们可以通过打算之前scaledDensity和density的比得到现在的scaledDensity,办法如下:
但是测试后创造其余一个问题,便是如果在系统设置中切换字体,再返回运用,字体并没有变革。于是还得监听下字体切换,调用 Application#registerComponentCallbacks 注册下 onConfigurationChanged 监听即可。
因此终极方案如下:
当然以上代码只因此设计图宽360dp去适配的,如果要以高维度适配,可以再扩展下代码即可。
Showcase适配前后和设计图比拟:
适配后各机型的显示效果: