前言
我们都知道哈,Android适配这块一直在困恼着我们.而之前的适配方案也很多
1.比如鸿洋的适配方案: 采用在Activity 创建转换xml的时候把系统的Layout换成自定义的,或者直接在xml中写自定义的>Layout.然后在onMeasure方法中直接遍历整颗树来调整 视图的大小
上面的这个方案还有一种就是写成一个工具类,在Activity setContentView方法之后调用,调整视图大小
但是上面两种的核心思想是一样的,都是对整颗树遍历调整为适配之后的大小
而写在 onmeasure 方法中的好处是每次有视图上的变化都嫩感知到,但是这同样是一个性能问题,每次有视图的改变都会调用.而工具类的形式的缺点也很明显也就是动态创建布局的时候就得手动调用一次
又比如动态生成各种限定符的values资源来达到适配,这个方法生成的那一刻是爽的,但是后期的维护是致命的,个人觉得如果这个方案是编译期间的一个工具就好了,怎么说呢,就是在编译的时候会根据我们写的dimen values文件去生成各种限定符的文件,然后参与编译之后就被删除乐,这样子既避免了后期维护的烦恼,也达到了可以适配所有项目的方式.但是!!敲黑板!!一个项目开发人员多了之后,你怎么限定他一定会把每一个 dimen 写到 dimen 文件中去.所有这是我能想到的缺点
今天带来的方案不是上面说的那样,而且没有入侵性的.开发者可以很轻松的视线适配所有屏幕,而且性能方面和适配前是一样的.我们先看一组适配的效果
效果(相同的分辨率:720 * 1280.不同的 DPI)
相同的分辨率:720 * 1280.不同的 DPI 160,240,320,420,480,560
六个设备从左到右的Api版本依次是:19,21,22,23,24,25
你可以很明显的看到在分辨率一样的前提下,随着 DPI 的增大,我们的应用的视图从小显示到大,最后一张甚至挤的变形了.
适配后的应用你可以看到在所有的设备上显示的都是一样的,轮播图那里不一样的原因最后阐述
适配前
适配后
效果(相同的DPI:160 .不同的分辨率)
相同的DPI:160 .不同的分辨率320*480,480*800,640*960,600*1024,720*1280,1080*1920
六个设备从左到右的Api版本依次是:19,21,22,23,24,25
适配前
适配后
效果(不同DPI.不同分辨率)
不相同的DPI 120,160,240,320,480,560不同的分辨率320*480,480*800,720*1280,800*1280,1080*1920,1440*2560六个设备从左到右的Api版本依次是:19,21,22,23,24,25
适配后(适配前的就不贴了,反正显示不正常你们也懂)
效果(超高分辨率1800 * 2880,不同dpi的适配前后对比图)
640 DPI
560 DPI
480 DPI
420 DPI
320 DPI
240 DPI
160 DPI
核心代码和思路
好了,我的测试就到这里了,下面贴出全部代码和使用方式,并且要注意的地方
public static void adapter (Context context) {
final float DESIGN_WIDTH = 750f;
final float DESIGN_HEIGHT = 1334f;
final float DENSITYDPI = 320f;
DisplayMetrics dm = activity.getResources().getDisplayMetrics();
float rate = 1f;
if (dm.widthPixels > dm.heightPixels) {
rate = dm.heightPixels / DESIGN_WIDTH;
} else {
rate = dm.widthPixels / DESIGN_WIDTH;
}
float targetDensityDpi = (int) (DENSITYDPI * rate + 0.5f);
// 这里是 DPI,表示的是每英寸有的像素点,
dm.densityDpi = targetDensityDpi; // 160
// 这里在Android里面的意思是表示 DP 和 PX 之间的比例
dm.density = targetDensityDpi / 160f; // 1,1.5,2
// 这个是表示 SP 和 PX 之间的比例
dm.scaledDensity = targetDensityDpi / 160f;
}
上述的代码中核心就是利用 UI 给的设计图的分辨率和DPI,然后根据运行的目标设备的真实分辨率计算出缩放比(一定要是最短的两边的比例,才能保证写的控件大小都是完美适配的)
然后调整系统的几个重要的参数
dm.densityDpi
系统根据这个值来选择资源文件的适配,也就是说这个值会影响到系统会选择使用资源中哪个 DPI 下的资源,比如160DPI会选择低分辨率的图片来显示,320DPI会选择高分辨的图片来显示,因为我们通常会在 drawable 文件夹中有多套资源
dm.density
系统用于转化 DP 的手段
dm.scaledDensity
系统用于转化 SP 的手段
系统源码
在类 TypedValue 中有一个单位转化的方法:
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 和 SP 的手段,换句话说,也就是我们把两个值根据真实设备分辨率调整为正确的大小,那么我们就可以视线完全适配,这也就是上述代码的一个核心思路.
支持的方面
可贵的是,用了这个适配方案,原先项目怎么写还是怎么写,不会影响代码的编写方式
- 支持Dialog
- 支持Fragment
- 支持Toast
- 支持第三方库
- 无需在代码的任何一个地方多次调用
- 无损性能
使用方式
- 在Application中onCreate方法中调用上述代码,修改 Application 中的配置,Toast这种能适配
- 在Application中调用方法registerActivityLifecycleCallbacks(ActivityLifecycleCallbacks callback)注册Activity生命周期,在onActivityCreated(Activity activity, Bundle savedInstanceState) 实现方法中调用上述代码,注意点看下文
- 或者你不在Application中注册Activity的生命周期的回调,那么可以在BaseActivity的onCreate 方法中写,注意点看下文
注意点
- 不管你调整系统参数的那段代码写在哪里,一定要保证每一个Activity的super.onCreate(savedInstanceState); 这句代码一定要先于 setContentView(R.layout.xxx) 调用;
- 横竖屏切换的时候其实也是适配的,只不过因为横屏的时候,视图本来就不是为了横屏设计的,所以整体看上去会怪怪的,但是内部的所有控件大小都是适配到位的,这种情况是因为你缺少了一个横屏的布局,不是适配的原因,这点请谅解
- 8.0之前Application中用的 DisplayMetrics 对象和每一个Activity中用的都是一样的,而8.0 中每一个Context都会持有一个新的,所以这也就是上述的代码需要对每一个Activity中的参数进行做调整的原因
总结
这个方案博主测试这么多,也是为了应用到我自己公司的项目中,博主还未发现有啥问题,如果您看到这里,如果有什么意见或者更好的方案,或者觉这个方案哪里有问题,烦请指出,谢谢!