在进行屏幕适配开始之前有必要先了解一下 dp、px、sp、dpi这些都是代表了什么
一、基本概念
dp:(device independent pixels)== dip 虚拟像素,用于限定控件尺寸
px:像素点
sp:同dp相似,只不过更多的是用于控件字体大小
dpi:(dots per inch)对角线每英寸的像素点的个数也就是像素密度
density:翻译过来就是 密度
二、计算公式
px = density * dp
density = dpi / 160
dpi = size ——屏幕尺寸(inch)
三、dp适配的效果图
这里看一下未进行适配时候的效果图 这三者的分辨率 分别为 400* 800(4.0) 1080 * 1920(4.95) 1440 * 2560 (5.96)
由于density不是固定不变的,所以每个分辨率不同的设备他们的density都肯定不相等,这样就会造成每个设备的宽/高对应的总dp都是不同的,上图中 dpi分别是:223、445、493对应的density是 1.4、2.78、3,那么总的dp = width(px)/density 分别为
286、388、480 图中TextView 使用了px像素, ImageView采用的是dp两者对比能看出来差距还是非常大的,dp单位在设备间的差距相对px是小很多的 这也就是为什么在做界面的时候没有人使用px而是使用dp的原因。
四、使用smallWidth适配
原理:限定符适配原理与屏幕分辨率限定符适配原理一样的,系统会根据规则去寻找res文件中匹配的 dimens.xml 资源文件。区别就在于屏幕分辨率限定符适配是拿 px 值等比例缩放,而 smallestWidth 限定符适配是改用了dp。
网上有很多现成的不同尺寸的value-sw**dp 可以直接copy 也可以通过插件的的方式一键生成
这里介绍一下生成插件生成的方式:
首先settings - plugins 找到SmallestWidth Dimens 插件 下载安装 重启AS
ALT + P 快捷键唤出插件 ,图中design width 设计稿像素 根据自己的来,默认输入360即可,最终生成对应的资源文件,直接使用就可以了
看效果:
验证方案:(系统在寻找对应资源文件的时候,如果找不到对应会就近选择一个相近的资源文件使用)
400 * 800 分辨率的手机,dpi为223,我们添加了一个200dp的控件
density = dpi / 160 = 1.4
屏幕总宽度 dp = 400px / 1.4 = 285
资源文件value-sw300dp sw-200dp = 155.6dp
计算px = dp * density = 217.84
屏幕占比:217.84 / 400 = 0.54
1080 * 1920 分辨率的手机,dpi 为 445,同样是200dp的控件
density = dpi / 160 = 2.78
屏幕总宽度 dp = 1080px / 2.78 = 388
资源文件value-sw380dp sw200dp = 211.2dp
计算px = 211.2 * 2.78 = 587.136
屏幕占比:587.136 / 1080 = 0.54
1440 * 2560 分辨率的手机,dpi为493 ,同样的200dp控件
density = 493 / 160 = 3
屏幕总宽度dp = 1440px / 3 = 467
资源文件value-sw460dp sw200dp = 255.6dp
计算px = 255.6 * 3 = 766.8
屏幕占比:766.8 / 1440 = 0.53
通过上述计算 最终得到的屏幕占比 误差几乎可以忽略不计,可以达到很好的屏幕适配。
优点
- 非常稳定,极低概率出现意外
- 不会有任何性能的损耗
- 适配范围可自由控制,不会影响其他三方库
- 在插件的配合下,学习成本低
缺点
- 侵入性高,在所有地方都需要引用。
- 还是没有办法覆盖所有的机型分辨率,部分机型可能适配效果还是不佳
- 不能以高度为基准进行适配
- 生成很多文件,增大APP体积1~2M
五、今日头条适配方案
今日头条屏幕适配方案的核心原理在于,根据公式计算出density
假设我们的设计图宽度是360dp 那么density = 屏幕宽度 / 360 ,得到这个数值之后通过系统的api接口来进行数值修改,达到屏幕适配的目的
验证方案:
400 * 800分辨率的手机,设置一个TextView为200dp宽,设计图宽度为360dp
density = (屏幕宽度 400 )/ 360 = 1.11
view宽度 = 1.11 * 200dp = 222.22px
屏幕占比 = 222.22px / 400 px = 0.56
1080* 1920分辨率的手机,设置一个TextView为200dp宽,设计图宽度为360dp
density = (屏幕宽度 1080)/ 360 = 3
view宽度 = 3* 200dp = 600 px
屏幕占比 = 600px / 1080px = 0.56
1440* 2560 分辨率的手机,设置一个TextView为200dp宽,设计图宽度为360dp
density = (屏幕宽度 1440)/ 360 = 4
view宽度 = 4* 200dp = 800 px
屏幕占比 = 800px / 1440px = 0.56
最终我们通过计算得到的数值都是相等的,没有一点误差
优点
- 使用成本非常低,操作非常简单
- 侵入性非常低
- 可适配三方库的控件和系统的控件
缺点
- 会全局影响APP的控件大小,例如一些第三方库控件,他们设计的时候可能设计图尺寸并不是像我们一样是375dp,这样就会导致控件大小变形等一些问题。
那么如何修改系统的density,直接在activity的oncreate方法中运行下面的方法即可
/**
* 屏幕适配
* 设计图宽360dp
*/
private static float sNoncompatDensity;
private static float sNoncompatScaledDensity;
public static void setCustomDensity(@NonNull Activity activity, @NonNull final Application application) {
DisplayMetrics appDisplayMetrics = application.getResources().getDisplayMetrics();
if (sNoncompatDensity == 0) {
sNoncompatDensity = appDisplayMetrics.density;
sNoncompatScaledDensity = appDisplayMetrics.scaledDensity;
application.registerComponentCallbacks(new ComponentCallbacks() {
@Override
public void onConfigurationChanged(Configuration newConfig) {
if (newConfig != null && newConfig.fontScale > 0) {
sNoncompatScaledDensity = application.getResources().getDisplayMetrics().scaledDensity;
}
}
@Override
public void onLowMemory() {
}
});
}
float targetDensity = appDisplayMetrics.widthPixels / 360;
float targetScaledDensity = targetDensity * (sNoncompatScaledDensity / sNoncompatDensity);
int targetDensityDpi = (int) (160 * targetDensity);
appDisplayMetrics.density = targetDensity;
appDisplayMetrics.scaledDensity = targetScaledDensity;
appDisplayMetrics.densityDpi = targetDensityDpi;
DisplayMetrics activityDisplayMetrics = activity.getResources().getDisplayMetrics();
activityDisplayMetrics.density = targetDensity;
activityDisplayMetrics.scaledDensity = targetScaledDensity;
activityDisplayMetrics.densityDpi = targetDensityDpi;
}
总结:通过对上述两种屏幕适配方式的对比,smallWidth存在一定的差异,而通过修改density的方式能够完美实现屏幕的适配,我们做屏幕适配最终的目的就是实现在不同尺寸的手机上,同一个控件在屏幕的占比相等,所以更优选的方案还是第二种方案
参考链接:Android屏幕适配方案分析