现在的Android市场,手机碎片化真的是不敢恭维,屏幕分辨率,尺寸,各式各样,虽然满足了广大消费者,但是却也搞得广大android开发者头疼,如何才能对这些手机进行完美的适配呢。
官方给出了一个建议,dp适配,据说以dp为单位能够在不同的手机上显示相似的结果,感觉可以一劳永逸了,但是却也不尽然,首先需要分析一下dp的适配方案:
其实手机才不认识dp这个单位呢,它只认识px,所以dp最终还是会转化为px的,那他们的关系怎么换算呢:
density = px/dp;
density = dpi/160;
px = dp * (dpi/160);
在dpi =160的时候,1dp 大约为1px,这是谷歌给出的,那为什么说dp无法适配所有的手机呢,比如咋相同分辨率的手机上,1280 * 720,如果屏幕的尺寸不一样的话,dpi的值就不一样了,因为:
明白了吧,就算分辨率一样,如果屏幕尺寸不一样,dpi也不一样,所以dp无法解决所有的屏幕适配;
那有没有更好的方式去适配呢,有,百分比适配,可能大家也听说过,比如鸿洋的百分比适配方案,的确很不错,至少在原理上已经指明了方向,通过百分比适配,我们就可以更好的适配所有的手机啦,但是鸿洋的百分比实现方案也有一些弊端,该方案的基础是在预先知道了屏幕的分辨率的情况下预先建立对应的适配文件,通过生成对应的适配文件,这种方案会增加apk的体积,有些我们没有使用的尺寸也会被加入进去,或者去继承百分比适配的view,或多或少都会比较麻烦,鉴于此,我们需要从dp的适配原理去分析一下,该如何更好地实现百分比适配;
那么我们通过dp最后是怎么被转化为px的呢,通过代码追踪,最后发现,其实最后都是通过TypeValue的applydimension的方法转化过来的:
public static float applyDimension(int unit, float value,
DisplayMetrics metrics)
{
switch (unit) {
case COMPLEX_UNIT_PX:
return value;
case COMPLEX_UNIT_DIP:
return value * metrics.density;
case COMPLEX_UNIT_SP:
return value * metrics.scaledDensity;
case COMPLEX_UNIT_PT:
return value * metrics.xdpi * (1.0f/72);
case COMPLEX_UNIT_IN:
return value * metrics.xdpi;
case COMPLEX_UNIT_MM:
return value * metrics.xdpi * (1.0f/25.4f);
}
return 0;
}
我们写的dp最后被转化为 value * metrics.density,sp最后被转化为:value * metrics.scaledensity,这个metrics就是一个DisplayMetrics对象,并且我们居然也可以改变这个density的值。
那么如果我们想要转为百分比,这个density的值该是多少呢,这个就合切图的标准有关了,比如切图的宽是360dp,那么我们就可以以360dp为基准得到:
density = 宽(px)/ 360;
我们从新设置这个值给metrics,那么我们的dp适配不就转化为百分比适配了嘛,sp也是一样的,下面直接给出我的类:
public class ScreenMatcher {
//切图基准
private static final float with = 375L;
private static final float height = 667L;
//原始值
private static float mSrcDensity;
private static float mSrcScaledDensity;
private static int mSrcDensityDpi;
//修正值
private static float mFixDensity;
private static float mFixScaledDensity;
private static int mFixDensityDpi;
public static void fix(final Application application) {
DisplayMetrics metrics = application.getResources().getDisplayMetrics();
if (mSrcDensity == 0) {
mSrcDensity = metrics.density;
mSrcScaledDensity = metrics.scaledDensity;
mSrcDensityDpi = metrics.densityDpi;
application.registerComponentCallbacks(new ComponentCallbacks() {
@Override
public void onConfigurationChanged(Configuration newConfig) {
if (newConfig != null && newConfig.fontScale > 0) {
mSrcScaledDensity = application.getResources().getDisplayMetrics().scaledDensity;
}
}
@Override
public void onLowMemory() {
}
});
}
float hDensity = metrics.heightPixels / height;
float wDensity = metrics.widthPixels / with;
mFixDensity = hDensity < wDensity ? hDensity : wDensity;
mFixScaledDensity = mFixDensity * (mSrcScaledDensity / mSrcDensity);
mFixDensityDpi = (int) (mFixDensity * 160);
metrics.density = mFixDensity;
metrics.scaledDensity = mFixScaledDensity;
metrics.densityDpi = mFixDensityDpi;
}
}
虽然我们只需要按照屏幕宽或者高就可以,但是实战分析,我觉得使用更小的会好一点。
然后在Activity的onCreate的方法中直接调用:
ScreenMatcher.fix(this);
就完成了我们的适配,是不是很简单,由于如果通过系统设置大小时,我们的字体不会改变,所以需要通过application.registerComponentCallbacks方法去改变scaledDensity,如果你不需要去改变你的字体大小,可以去掉。
我们的切图基准是375DP * 667DP,你需要换成你们自己的即可。