android中的dp在渲染前会将dp转为px,计算公式:
-
px = density * dp;
-
density = dpi / 160;
-
px = dp * (dpi / 160)
而dpi是根据屏幕真实的分辨率和尺寸来计算的,每个设备都可能不一样的。
1.屏幕分辨率限定符适配
根据当前市面上手机的屏幕的分辨率创建不同的文件夹,系统运行的时候,会自动去选择读取对应的文件夹中的xml,即每种屏幕分辨率的设备需要定义一套 dimens.xml 文件
缺点是:假设我们UI设计图是按屏幕宽度为360dp来设计的,那么在上述设备上,屏幕宽度其实为1080/(440/160)=392.7dp,也就是屏幕是比设计图要宽的。这种情况下, 即使使用dp也是无法在不同设备上显示为同样效果的。 同时还存在部分设备屏幕宽度不足360dp,这时就会导致按360dp宽度来开发实际显示不全的情况
而且上述屏幕尺寸、分辨率和像素密度的关系,很多设备并没有按此规则来实现, 因此dpi的值非常乱,没有规律可循,从而导致使用dp适配效果差强人意。
2.今日头条适配方案(字节跳动)
在上面的两种适配方案中,都是根据不同的屏幕大小设置不同的控件的宽高值。在公式 px =dp * density 中,不同屏幕的dp,以及density都是不同,从而实现不同的手机渲染有着不同的px。在今日头条的适配方案的思路是假定每个手机的屏幕宽度是固定的。比如设计稿宽度是360dp, 想要保证在所有设备计算得出的px值都正好是屏幕宽度的话,只能修改 density 的值。
在xml中设置宽高后,都是通过以下代码进行转换:
http://androidxref.com/9.0.0_r3/xref/frameworks/base/core/java/android/util/TypedValue.java#applyDimension
当在xml中设置的单位为px, 则直接用对应px的值;当单位为dp,则返回的是:value * metrics.density;当单位为sp,则返回的是value * metrics.scaledDensity。今日头条选用dp或者pt作为适配单位,给出的理由是项目中大部分都是使用dp做单位。
如果UI小姐姐给的设计图是 360dp(宽) x 640dp(高) 的设计图,如果要适配所有的屏幕,则 density = 设备屏幕的真实宽度(单位:px) / 360。这里为什么会是这样呢?上面公式中:px =dp * density,假定了所有的屏幕的宽度都是360dp,而px的值就是设备屏幕的真实宽度,所以就有了:density = 设备屏幕的真实宽度(单位:px) / 360。这样1dp在所有屏幕的宽中所占的比率都是一样的,都是1/360,在xml中就可以按照设计图来定义了。
而对于字体,Google推荐设置的单位为sp,在上面的源码中,字体的转换公式为:px= value * metrics.scaledDensity,这里的value 是在xml中定义的以sp为单位的值。一般情况下,scaledDensity 的值与 density 是相等的。但是如果在系统中设置了改变了字体的大小,scaledDensity 的值与 density 就不相等了。scaledDensity = 人为修改的density * (系统的ScaledDensity / 系统的Density)。如果不需要字体大小随系统设置而改变,就直接使用dp做单位好了.
/**
* 使用 dp 作为适配单位(适合在新项目中使用,在老项目中使用会对原来既有的 dp 值产生影响)
* <br>
* <ul>
* dp 与 px 之间的换算:
* <li> px = density * dp </li>
* <li> density = dpi / 160 </li>
* <li> px = dp * (dpi / 160) </li>
* </ul>
*
* @param context
* @param designSize 设计图的宽/高(单位: dp)
* @param base 适配基准
*/
private static void matchByDP(@NonNull final Context context, final float designSize, int base) {
final float targetDensity;
if (base == MATCH_BASE_WIDTH) {
targetDensity = sMatchInfo.getScreenWidth() * 1f / designSize;
} else if (base == MATCH_BASE_HEIGHT) {
targetDensity = sMatchInfo.getScreenHeight() * 1f / designSize;
} else {
targetDensity = sMatchInfo.getScreenWidth() * 1f / designSize;
}
final int targetDensityDpi = (int) (targetDensity * 160);
final float targetScaledDensity = targetDensity * (sMatchInfo.getAppScaledDensity() / sMatchInfo.getAppDensity());
final DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
displayMetrics.density = targetDensity;
displayMetrics.densityDpi = targetDensityDpi;
displayMetrics.scaledDensity = targetScaledDensity;
}
从这两次的效果图,可以很明显的看出加了适配方案后,两个屏幕中控件的大小差不多一致了。此套方案对于原来的老项目不太友好,因为改了屏幕的density,所有布局的实际尺寸都会发生变化,整个布局中的所有尺寸都要进行修改一遍。
ps:以上被整理成了一个单独适配屏幕的类,下载地址是:https://download.youkuaiyun.com/download/and_caicai/12412196