背景
Android碎片化严重,屏幕分辨率千奇百怪,虽然官方提供了dp和sp,但是适配还是不尽人意,下面提供一种简单且无侵入的适配方案
必备知识点
Android会在渲染前将dp或者sp都会转化为px,计算公式:
px = dp * density;
px = sp * scaledDensity;
density = dpi / 160;
所以: px = dp * (dpi / 160)
其中:dpi(像素密度)根据分辨率和屏幕尺寸计算出来的
示例:屏幕分辨率为:1920*1080,屏幕尺寸为5吋的话,那么dpi为440
存在的问题
假设我们UI设计图是按屏幕宽度为360dp来设计的,那么在上述设备上,屏幕宽度其实为1080/(440/160)=392.7dp,也就是屏幕是比设计图要宽的。这种情况下, 即使使用dp也是无法在不同设备上显示为同样效果的。 同时还存在部分设备屏幕宽度不足360dp,这时就会导致按360dp宽度来开发实际显示不全的情况。
而且上述屏幕尺寸、分辨率和像素密度的关系,很多设备并没有按此规则来实现, 因此dpi的值非常乱,没有规律可循,从而导致使用dp适配效果差强人意
解决方案:
由于px = dp * density;如果设计图宽为360dp,想要保证在所有设备计算得出的px值都正好是屏幕宽度的话,我们只能修改 density 的值;
density 是 DisplayMetrics 中的成员变量,而 DisplayMetrics 实例通过Resources#getDisplayMetrics 可以获得,而Resouces通过Activity或者Application的Context获得。
先来熟悉下 DisplayMetrics 中和适配相关的几个变量:
DisplayMetrics#density 就是上述的density
DisplayMetrics#densityDpi 就是上述的dpi
DisplayMetrics#scaledDensity 字体的缩放因子,正常情况下和density相等,但是调节系统字体大小后会改变这个值
我们知道布局文件最后都是通过
package android.util;
public class TypedValue {
public static float applyDimension(@ComplexDimensionUnit 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;
}
}
方案就是把所有的屏幕都按照360dp或者其它固定的dp做处理,通过修改density值,保证dp转换后的px大小都是一致的
density = 设备真实宽(单位px) / 360
在基类activity和基类fragment中在onCreatef方法中处理
private static void adaptScreenVertical(Activity activity) {
DisplayMetrics appDisplayMetrics = activity.getApplicationContext().getResources().getDisplayMetrics();
if (sNoncompatDensity == 0) {
sNoncompatDensity = appDisplayMetrics.density;
sNoncompatScaledDensity = appDisplayMetrics.scaledDensity;
activity.getApplication().registerComponentCallbacks(new ComponentCallbacks2() {
@Override
public void onTrimMemory(int level) {
}
@Override
public void onConfigurationChanged(@NonNull Configuration newConfig) {
if (newConfig != null && newConfig.fontScale > 0) {
sNoncompatScaledDensity = activity.getApplication().getResources().getDisplayMetrics().scaledDensity;
}
}
@Override
public void onLowMemory() {
}
});
}
/**
* px = dp * (dpi / 160)
*
* density = dpi / 160
*
* px = dp * density
*
* dpi 像素密度- 每英寸的像素点数,数值越高显示越细腻
* dpi= (宽^2 + 高^2) = 对象线= 2203 1080 1920
* 屏幕大小是5英寸,所以2203/5 440
*
*
*
*/
final float targetDensity = appDisplayMetrics.widthPixels / 360;
final float targetScaleDensity = targetDensity * (sNoncompatScaledDensity / sNoncompatDensity);
final int targetDensityDpi = (int) (targetDensity * 160);
appDisplayMetrics.density = targetDensity;
appDisplayMetrics.scaledDensity = targetScaleDensity;
appDisplayMetrics.densityDpi = targetDensityDpi;
Log.d("getDensity", "app: " + targetDensity + "/scale=" + targetScaleDensity + "/dpi=" + targetDensityDpi);
DisplayMetrics activityDisplayMetrics = activity.getResources().getDisplayMetrics();
activityDisplayMetrics.density = targetDensity;
activityDisplayMetrics.scaledDensity = targetScaleDensity;
activityDisplayMetrics.densityDpi = targetDensityDpi;
}
参考文档:字节跳动技术方案