Android 屏幕适配问题的由来
我们都知道 Android 碎片化问题令人痛心疾首,而造成的屏幕差异正式碎片化的问题中心。
屏幕的尺寸从3英寸到10英寸,分辨率从320到1920应有尽有,这对我们ui适配问题造成很大的困难。对于屏幕碎片化问题,Android 官方推荐使用dp作为尺寸单位来适配ui,因此我们很有必要清楚px,dp,dpi,ppi,density这些概念。
定义 | 概念 | 转化 |
---|---|---|
px | 像素点,比如手机分辨率320 x 480表示宽有320像素,高有480像素 | px = density * dp |
ppi | 像素密度,每英寸包含的像素数目,这是屏幕物理参数,例如mate 20 pro 的ppi是538 | ppi约等于ddpi |
dpi | 像素密度,跟ppi不同的是,dpi可能被人为调整,例如几部相同分辨率不同尺寸的手机ppi是430,440,450,android会dpi指定为480 | dpi 约等于ppi |
dp | density-independent pixels,基于屏幕物理分辨率一个抽象单位,用来说明与密度无关的尺寸 | px = dp *(dpi / 160) |
density | 密度,屏幕每平方英寸还有的像素点数量 | density = dpi/160) |
一般来说,通过dp和自适应布局可以基本解决屏幕碎片化问题,这也是Android 推荐使用的屏幕兼容性方案,但是它也会存在两个比较大的问题:
- 不一致性。因为dpi与ppi的差异实际的差异性,导致在相同分辨率的手机上,控件实际大小会有所不同。
- 效率。设计师的设计稿都是由px为单位的,开发人员为了适配需要换算成dp。
除了dp适配之外,今天我要讲的是通过修改系统density来适配。
在编写xml文件时候,无论我们给控件的宽高用dp还是px,还是pt,最终android会把它转换成px显示。通过一下源码可以得知。
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时候,系统都会讲我们dp 值 乘以metrics.density换算的px值显示到手机上,sp的话则是乘以metrics.scaledDensity,默认情况下,metrics.scaledDensity = metrics.density。所以我们只要通过修改系统的metrics.density 就能达到适配的效果。
那么怎么修改呢?我们可以让ui出图时候定一个参考的宽度的值,比如width = 320dp;然后我们可以根据当前手机的displayMetrics.widthPixels / width算出density从而替换系统的density。话不多说上代码~
public class Density {
private static final float WIDTH = 320;//参考设备的宽
private static float appDensity;//表示屏幕密度
private static float appScaleDensity; //字体缩放比例,默认appDensity
public static void setDensity(final Application application, Activity activity){
//获取当前app的屏幕显示信息
DisplayMetrics displayMetrics = application.getResources().getDisplayMetrics();
if (appDensity == 0){
//初始化赋值操作
appDensity = displayMetrics.density;
appScaleDensity = displayMetrics.scaledDensity;
//计算目标值density, scaleDensity, densityDpi
float targetDensity = displayMetrics.widthPixels / WIDTH; // 1080 / 360 = 3.0
float targetScaleDensity = targetDensity * (appScaleDensity / appDensity);
int targetDensityDpi = (int) (targetDensity * 160);
//替换Activity的density, scaleDensity, densityDpi
DisplayMetrics dm = activity.getResources().getDisplayMetrics();
dm.density = targetDensity;
dm.scaledDensity = targetScaleDensity;
dm.densityDpi = targetDensityDpi;
}
}
根据当前手机宽的像素值/参考的宽度值 算出目标density,然后替换掉系统的即可。
然后mainActivity 里调用Density.setDensity(getApplication(),this);