一,如何使用
今日头条适配方案默认项目中只能以高或宽中的一个作为基准,进行适配。这里width可以是宽或高
1,全局使用,作用于整个项目,当然也可以只作用于局部,如:activity。其中width怎么得来的,以设计图1080*1920为例,按照XHDPI 2px=1dp,这样setDensity的值width就是540dp。凡是UI标注的px除以2就是布局中要写的dp值。
public class MyApplication extends Application {
private float width;//设计图屏幕宽度 单位dp
@Override
public void onCreate() {
super.onCreate();
Log.e("MyApplication", "onCreate: ");
Density.setDensity(this, width);
}
}
2,Density工具类代码
public class Density {
private static float appDensity;
private static float appScaledDensity;
private static DisplayMetrics appDisplayMetrics;
/**
* 用来参照的的width
*/
private static float WIDTH;
public static void setDensity(@NonNull final Application application, float width) {
appDisplayMetrics = application.getResources().getDisplayMetrics();
WIDTH = width;
registerActivityLifecycleCallbacks(application);
if (appDensity == 0) {
//初始化的时候赋值
appDensity = appDisplayMetrics.density;
appScaledDensity = appDisplayMetrics.scaledDensity;
//添加字体变化的监听
application.registerComponentCallbacks(new ComponentCallbacks() {
@Override
public void onConfigurationChanged(Configuration newConfig) {
//字体改变后,将appScaledDensity重新赋值
if (newConfig != null && newConfig.fontScale > 0) {
appScaledDensity = application.getResources().getDisplayMetrics().scaledDensity;
}
}
@Override
public void onLowMemory() {
}
});
}
}
private static void setDefault(Activity activity) {
setAppOrientation(activity);
}
private static void setAppOrientation(@Nullable Activity activity) {
float targetDensity = 0;
try {
targetDensity = appDisplayMetrics.widthPixels / WIDTH;
} catch (NumberFormatException e) {
e.printStackTrace();
}
float targetScaledDensity = targetDensity * (appScaledDensity / appDensity);
int targetDensityDpi = (int) (160 * targetDensity);
/**
*
* 最后在这里将修改过后的值赋给系统参数
*
* 只修改Activity的density值
*/
DisplayMetrics activityDisplayMetrics = activity.getResources().getDisplayMetrics();
activityDisplayMetrics.density = targetDensity;
activityDisplayMetrics.scaledDensity = targetScaledDensity;
activityDisplayMetrics.densityDpi = targetDensityDpi;
}
private static void registerActivityLifecycleCallbacks(Application application) {
application.registerActivityLifecycleCallbacks(new Application.ActivityLifecycleCallbacks() {
@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
setDefault(activity);
}
@Override
public void onActivityStarted(Activity activity) {
}
@Override
public void onActivityResumed(Activity activity) {
}
@Override
public void onActivityPaused(Activity activity) {
}
@Override
public void onActivityStopped(Activity activity) {
}
@Override
public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
}
@Override
public void onActivityDestroyed(Activity activity) {
}
});
}
}
二,原理讲解
1,Android中px和dp的转换
我们知道在开发中,不管我们在布局中写多少dp,最后都会转化成px。
2,Android自身设定的屏幕密度
①,安卓尺寸众多,按每个屏幕去适配肯定不现实,所以为了解决这个问题,安卓手机屏幕有自己初始的固定密度,安卓会根据这些屏幕不同的密度自己进行适配
②,安卓分辨率五花八门,如何计算密度数。
3,传统的适配方式
经典的适配方式我们都是通过宽高限定符,在资源文件下生成不同分辨率的资源文件,然后在布局文件中引用对应的 dimens。但是这种方式未免太啰嗦,市面上有各种各样的手机,不能每种机型都去创建资源文件。
src/main
res
- - values
- - values-800x480
- - values-860x540
- - values-1024x600
- - values-1024x768
- - …
- - values-2560x1440
4,核心原理
①,density=当前设备屏幕总宽度(单位为px)/ 设计图总宽度(单位为 dp),为什么要算出density。在安卓源码TypedValue类中可以看到这段代码,我们通常用此方法进行dp和px的转化。density 的意思就是 1 dp 占当前设备多少像素,我们知道density 在每个设备上都是固定的。因此可以得出DPI / 160 = density,而屏幕的总 px 宽度 / density = 屏幕的总 dp 宽度,最终 dp=(px/dpi)*160
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;
}
②,由上面公式可以分别算出 屏幕宽度为1080和1440的dp,可以看出屏幕不同对应的总宽dp也不同。但是我们布局中的dp是相同的,这样就会导致控件在不同设备上所占比例不同出现变形,因此我们才需要适配。如何适配才能达到效果呢,如果保证让屏幕总dp宽度不变,就会达到适配的效果。
屏幕宽度1080,DPI 480 , 屏幕总 dp 宽度为 (1080/ 480 )* 160 = 360dp
屏幕宽度1440,DPI 560 , 屏幕总 dp 宽度为 (1440 / 560 )* 160 = 411dp
③,屏幕的总 px 宽度 / density = 屏幕的总 dp 宽度 , 可以看出px肯定是变化的,那么只能改变density 值才能保证dp不变。因此**density=当前设备屏幕总宽度(单位为px)/ 设计图总宽度(单位为 dp)**就派上了用场。
例1:设计图总宽度为375dp,屏幕宽度为1080px,可以得出density,1080/375=2.88。假如一个view为60dp60dp,那么算成px就是 60dp2.88=172.8px,所占屏幕宽度比为 172.8/1080=0.16
例2:设计图总宽度为375dp,屏幕宽度为1440px,可以得出density,1440/375=3.84。假如一个view为60dp60dp,那么算成px就是 60dp3.84=230.4px,所占屏幕宽度比为 172.8/1440=0.16
由示例1,2可以看出,虽然屏幕宽度不一样,但是都实现了屏幕等比例适配。由于计算没用到DPI,即使手机DPI不同也能完美实现适配
三,优缺点
1,优点
- 使用起来简单,可以全局配置省去很多人工成本。
- 使用系统API实现,减少性能损耗。
2,缺点
- 如果是在老项目的基础上做会造成很大影响,不适合老项目。
- 如果是第三方控件和自己的设计图不一致也会有影响。
总结:
-
今日头条适配是以设计图的宽或高进行适配的,适配最终是改变系统density实现的。
-
由于手机密度不同,通过修改系统密度,保证view所占比例不变,从而实现适配效果。