前言:前段时间在一个技术网站中看到一篇文章:一种极低成本的Android屏幕适配方式,原文地址:http://www.apkbus.com/blog-822415-77856.html,拜读后感觉方法很独特,直接从源码着手解决问题,动手实践了一下,出现了一些问题,解决后感觉适配效果很好,于是推荐给小伙伴们使用,但是很多都说看不懂,不知道怎么使用,所以决定写一点使用心得分享给大家,注:本文只教大家如何使用,如想了解实现原理请移步原文地址
首先来看原文最终方案代码:
你没有看错,就是这么简单,就这些代码就能实现适配???,是不是感觉贼不靠谱的样子,好吧,其实我也不信,于是照着这张图片手打下来(ps:真是纯手打的,因为他的原文终极解决方案代码就是一张图片...放心我不会也这么给你们一张图片的,待会儿放源码),放在基类(BaseActivity)中,
在这里要注意了:appDisplayMetrice.widthPixels/360,这个360是你设计图的宽度的dp大小,一定要根据你设计图的标准来写!!!
那这个设计图的宽度dp怎么根据自己的设计图上的标准来得到呢,很简单,只要得出设计图的比例density就O98K了
density的计算公式为:density = dpi / 160,想要得到density,那就必须要先得到dpi,而dpi计算公式为:
好了,公式咱都有了,那么就开始套公式吧,比如我现在的设计图的分辨率为750*1334,屏幕尺寸为4.7(不知道的可以去问UI)
根据公式得出dpi= 326 ,那么density =326 /160 ≈= 2 ,很好,density有了,那么上图中的设计图的宽度dp就为 750/2 = 375
附上代码:
private static float sNoncompatDesity;
private static float sNoncompatScaledDesity;
private static void setCustomDesity(@NonNull Activity activity, @NonNull final Application application){
final DisplayMetrics appdisplayMetrics = application.getResources().getDisplayMetrics();
if(sNoncompatDesity==0){
sNoncompatDesity = appdisplayMetrics.density;
sNoncompatScaledDesity = appdisplayMetrics.scaledDensity;
application.registerComponentCallbacks(new ComponentCallbacks() {
@Override
public void onConfigurationChanged(Configuration configuration) {
if(configuration!=null&&configuration.fontScale>0){
sNoncompatScaledDesity = application.getResources().getDisplayMetrics().scaledDensity;
}
}
@Override
public void onLowMemory() {
}
});
}
final float targetDesity = appdisplayMetrics.widthPixels/375;//375 设计图的宽度dp
final float targetScaleDesity = targetDesity*(sNoncompatScaledDesity/sNoncompatDesity);
final int targetDesityDpi = (int)(160*targetDesity);
appdisplayMetrics.density = targetDesity;
appdisplayMetrics.scaledDensity = targetScaleDesity;
appdisplayMetrics.densityDpi = targetDesityDpi;
final DisplayMetrics activityDisplayMetrics = activity.getResources().getDisplayMetrics();
activityDisplayMetrics.density = targetDesity;
activityDisplayMetrics.scaledDensity = targetScaleDesity;
activityDisplayMetrics.densityDpi = targetDesityDpi;
}
然后在BaseActivity中的onCreate方法中调用该方法,项目跑起来运行在手机上,好像有点毛病,比平常的尺寸要小了点,于是多运行了几款测试机,大部分手机貌似都有这个问题,而且在其中一款手机上显示的特别小,适配的效果明显不对,怎么办,这种适配方式不行吗,于是私信文章发布者,可惜没回复我,于是自己慢慢排查问题,折腾许久,终于发现了一个不起眼的问题
final float targetDesity = appdisplayMetrics.widthPixels/375;
问题出现在这行代码中,这行代码一眼看上去好像没问题,2个数字相除得到一个float类型的结果,但是appdisplayMetrics.widthPixels这个值是int类型,375也是int类型,int整数除以int整数得到的一定是int整数,原来如此,就比如说我明明预想中得到的结果应该为1.8 ,结果却给我变为了1,这怎么可能不出问题呢,所以这行代码正确写法应该是这样的:
final float targetDesity = (float)appdisplayMetrics.widthPixels/375;
再次编译运行,适配完美!
最终代码:
private static float sNoncompatDesity;
private static float sNoncompatScaledDesity;
private static void setCustomDesity(@NonNull Activity activity, @NonNull final Application application){
final DisplayMetrics appdisplayMetrics = application.getResources().getDisplayMetrics();
if(sNoncompatDesity==0){
sNoncompatDesity = appdisplayMetrics.density;
sNoncompatScaledDesity = appdisplayMetrics.scaledDensity;
application.registerComponentCallbacks(new ComponentCallbacks() {
@Override
public void onConfigurationChanged(Configuration configuration) {
if(configuration!=null&&configuration.fontScale>0){
sNoncompatScaledDesity = application.getResources().getDisplayMetrics().scaledDensity;
}
}
@Override
public void onLowMemory() {
}
});
}
final float targetDesity = (float)appdisplayMetrics.widthPixels/375;//375 设计图的宽度dp
final float targetScaleDesity = targetDesity*(sNoncompatScaledDesity/sNoncompatDesity);
final int targetDesityDpi = (int)(160*targetDesity);
appdisplayMetrics.density = targetDesity;
appdisplayMetrics.scaledDensity = targetScaleDesity;
appdisplayMetrics.densityDpi = targetDesityDpi;
final DisplayMetrics activityDisplayMetrics = activity.getResources().getDisplayMetrics();
activityDisplayMetrics.density = targetDesity;
activityDisplayMetrics.scaledDensity = targetScaleDesity;
activityDisplayMetrics.densityDpi = targetDesityDpi;
}
到了这里,有人就要问了,如果我这些代码都写好了,那么我在xml上面如何写dp大小呢?很简单,上面我们不是根据设计图的尺寸得出了density 等于2吗,那么比如设计图上标注一个TextView字体大小为30px 。则实际填写的字体大小为: 30 /2=15dp,根据这个规则来换算出来就好了
总结
1. 把最终代码放在BaseActivity中
2. 根据设计图尺寸得出比例density(如果实在不想算,想直接知道已知分辨率和屏幕尺寸设计图的density,请查看:https://material.io/tools/devices/,如果进不去,请翻墙)
3. 用设计图屏幕宽的分辨率/density得出该设计图宽的dp并赋值到原来的值中
4. 在BaseActivity的onCreate方法中调用
5. xml中的dp-size根据设计图尺寸得出的比例density的值来赋值,px-size/density = dp-size
拓展
上面的最终方案是以屏幕宽度来适配的,但是如果我想以高度来适配呢,比如有一个页面,在不使用ScrollView的情况下,想让所有的手机一屏就能显示完全,且不能有显示不全的情况,如果以宽度来适配的话是肯定会有适配问题的,所以应该要支持某些页面可以适当地使用以高度来适配:
public static float sNoncompatDesity;
public static float sNoncompatScaledDesity;
public static void setCustomDesity(@NonNull Activity activity, @NonNull final Application application,boolean isWidth){
final DisplayMetrics appdisplayMetrics = application.getResources().getDisplayMetrics();
if(sNoncompatDesity==0){
sNoncompatDesity = appdisplayMetrics.density;
sNoncompatScaledDesity = appdisplayMetrics.scaledDensity;
application.registerComponentCallbacks(new ComponentCallbacks() {
@Override
public void onConfigurationChanged(Configuration configuration) {
if(configuration!=null&&configuration.fontScale>0){
sNoncompatScaledDesity = application.getResources().getDisplayMetrics().scaledDensity;
}
}
@Override
public void onLowMemory() {
}
});
}
final float targetDesity;
if(isWidth){
targetDesity = (float) appdisplayMetrics.widthPixels/375;//375 设计图的宽度dp 根据宽度适配
}else{
targetDesity = (float) appdisplayMetrics.heightPixels/667;//667 设计图的高度dp 根据高度适配
}
final float targetScaleDesity = targetDesity*(sNoncompatScaledDesity/sNoncompatDesity);
final int targetDesityDpi = (int)(160*targetDesity);
appdisplayMetrics.density = targetDesity;
appdisplayMetrics.scaledDensity = targetScaleDesity;
appdisplayMetrics.densityDpi = targetDesityDpi;
final DisplayMetrics activityDisplayMetrics = activity.getResources().getDisplayMetrics();
activityDisplayMetrics.density = targetDesity;
activityDisplayMetrics.scaledDensity = targetScaleDesity;
activityDisplayMetrics.densityDpi = targetDesityDpi;
}
原理参考宽度dp,使用当前的设计图的高度px/density 就是当前的高度dp了,使用时,可以在BaseActivity中调用
setCustomDesity(this, App.getIntence(),true);
因为我们一般都是使用宽度来适配屏幕的,所以默认宽度适配,如果想在单独的Activity中使用高度适配时,可以在该Activity的onCreate方法中调用
setCustomDesity(this,App.getIntence(),false);