本篇文章已授权微信公众号 guolin_blog (郭霖)独家发布
1. 序言
- 屏幕适配哪家强:
① 到底哪一种屏幕适配最合适,仁者见仁智者见智.
② 开始我推荐dimens基于px的适配,后来我推荐dimens dp的适配,而如今我推荐今日头条适配(修改手机的设备密度 density)。 - 效果图多大才更好:
推荐1倍效果图,即采用 720 * 360 大小( 1280 *720:两倍图 \ 1920 * 1080: 三倍图),最主要的原因就是1px = 1dp,效果图标多大的px,布局就写多大dp。
2. 屏幕各项参数
- 像素 - px:
一个小黑点就是像素。
- 尺寸:
屏幕的对角线的长度。
- 分辨率:
整个屏幕一共有多少个点,也就是像素。
- 像素密度 - dpi:
1. 每英寸中的像素数。
2. 假如设备分辨率为320*240,屏幕长2英寸宽1.5英寸,dpi=320/2 = 240/1.5 =160。
3. 对应于DisplayMetrics类中属性densityDpi的值。
4. 当然这种宽和高的dpi都相同的情况现在已经很少见。
- 密度 - density:
1. 每平方英寸中的像素数。
2. density = dpi / 160 。
3. 对应于DisplayMetrics类中属性density的值 。
4. 可用于px与px与dip的互相转换 :dp = px / density 。
- 设备独立像素 - dip - dp:
- 不同设备有不同的显示效果,不依赖像素。
- dp = px / density
- dp = px / (dpi / 160)
- dpi(像素密度)为160 的设备上1dp = 1px。
- 放大像素 - sp:
用于字体显示。
- dp转px、px转dp
public class Dp2Px {
public static int dp2px(Context context, int dp) {
return (int) (dp * context.getResources().getDisplayMetrics().density + 0.5);
}
public static int px2dp(Context context, int px) {
return (int) (px / context.getResources().getDisplayMetrics().density + 0.5);
}
}
说明:0.5 是为了避免损失精度。
- 常见设备的dp、px、density的关系
① ldpi:
density:0.75
分辨率:240*320
关系:dp = px / 0.75
② mdpi:
density:1
分辨率:320 * 480
关系:dp = px / 1
③ hdpi:
density:1.5
分辨率:480 * 800
关系:dp = px / 1.5
④ xhdpi:
density:2.0
分辨率:720 * 1280
关系:dp = px / 2
⑤ xxhdpi:
density:3
分辨率:1080 * 1920
关系:dp = px / 3
- 代码获取参数值:
//以1280*720为基准:
//获取手机屏幕的宽和高
int widthPixels = getResources().getDisplayMetrics().widthPixels;
int heightPixels = getResources().getDisplayMetrics().heightPixels;
//density
float density = getResources().getDisplayMetrics().density;
//dpi
int densityDpi = getResources().getDisplayMetrics().densityDpi;
//1dp = 多少px
int px = Dp2Px.dp2px(this, 1);
//1px = 多少dp
int dp = Dp2Px.px2dp(this, 1);
04-30 23:29:15.606 2484-2484/com.example.testscreen E/MainActivity: widthPixels: 720
04-30 23:29:15.606 2484-2484/com.example.testscreen E/MainActivity: heightPixels: 1280
04-30 23:29:15.606 2484-2484/com.example.testscreen E/MainActivity: density: 2.0
04-30 23:29:15.606 2484-2484/com.example.testscreen E/MainActivity: densityDpi: 320
04-30 23:29:15.606 2484-2484/com.example.testscreen E/MainActivity: px:2
04-30 23:29:15.606 2484-2484/com.example.testscreen E/MainActivity: dp:1
-
详解屏幕尺寸、分辨率、像素密度三者关系:
-
举例说明:屏幕分辨率为:1920*1080,屏幕尺寸为5吋的话,那么dpi为440:
3. 适配分类
3.1. 图片适配
- 在AndroidStudio的资源目录res下有五个层级图片文件夹,分别用来存放不同分辨率的图片:
① drawable-ldpi :低分辨率(用的少了,一般不再用)
② drawable-mdpi:中分辨率
③ drawable-hdpi:高分辨率
④ drawable-xdpi:较高分辨率
⑤ drawable-xxdpi:超级高分辨率
⑥ drawable-xxxhpi:顶级分辨率
- 在对应的文件夹下放置不同分辨率的图片就可以很好的对图片进行适配。
- 随着屏幕越来越大,推荐xxdpi的一套切图,这样就可以向下和向上兼容,节省资源。
- 对于图标使用svg格式,对于图片仍然使用png,svg的图标大小约是png的1/4,在很大的项目中,图标有很多,这个时候svg的优势就凸显无疑了。
3.2. 布局适配
- 不同分辨率,创建不同的布局文件夹:
① layout-800 * 480
② layout-1280 * 720
......
- 手机会根据分辨率去找设定的不同大小的layout的布局。
- 实际开发中这种使用的情况非常少,因为占用太多资源,慎用。
3.3. 权重适配
- 当两个或者更多布局占满屏幕宽或高的时候,子布局可以使用权重适配,常见于LinearLayout线性布局中。
3.4. 尺寸适配(dimens百分比适配)
- 这种适配的原理就是将宽和高通过百分比的原理分割为若干份:
以400 * 320为基准,以宽举例:宽分为了320份:每份1.0px,那1280*720的宽的每份就是2.25px。
- 创建values-400*320文件夹,创建dimens.xml文件,设置代码如下:
2.0px
- 创建values-800*480文件夹,创建dimens.xml文件,设置代码如下:
3.0px
- 代码中使用:
R.dimen.x2
- 布局中使用:
@dimen/x2
- 详解dimens-px 适配:
原理:根据市面上手机分辨率的占比分析,我们把1280和720设定为一个基准,然后其他分辨率根据这个基准做适配。基准的意思(比如320*480的分辨率为基准)是:
① 宽为320,将任何分辨率的宽度分为320份,取值为x1到x320
② 长为480,将任何分辨率的高度分为480份,取值为y1到y480
例如对于800 * 480的宽度480:
1.5/dimen>
3.0px
4.5
6.0px
7.5px
可以看到x1 = 480 / 基准 = 480 / 320 = 1.5 ;它的意思就是同样的1px,在320/480分辨率的手机上是1px,在480/800的分辨率的手机上就是1*1.5px,px会根据我们指定的不同values文件夹自动适配为合适的大小。
- 自动生成 dimen-px 文件夹
① 首先下载jar包:
链接: http://pan.baidu.com/s/1crbwwI 密码: dxwa
② 其次解压查看jar运行说明.txt文件,定制以1280/720为基准的分辨率,操作方法:
在你下载后的文件夹里面 按住Shift+鼠标右击进入命令行对话框,输入
java -jar autolayout.jar 720 1280就会自动生成res文件夹(默认的是以1080/1280为基准,所以需要自己设置),假如你觉得这些value文件夹里面没有你想要的分辨率可以在制定基准分辨率的同时,添加额外的分辨率(比如400/600),输入java -jar autolayout.jar 720 1280 400,600,假如想多添加几个额外的分辨率(又想添加500*700)只需把额外的分辨率用下划线隔开即可,输入java -jar autolayout.jar 720 1280 400,600_500,700
③ 接着把res里面的value文件夹放到res下面即可
④ 标注图假如宽50px,高80px,我们只需要把宽高写为@dimen/x50,@dimen/y80即可 - 已经生成的dimen-px文件夹地址:
链接:https://pan.baidu.com/s/1jLXpQXs898hzIensZKV7gg 密码:6xgs
3.5 . 代码适配
Button button = (Button) findViewById(R.id.bt_main_button);
//获取手机屏幕的宽和高
int widthPixels = getResources().getDisplayMetrics().widthPixels;
int heightPixels = getResources().getDisplayMetrics().heightPixels;
//给button设置宽和高
ViewGroup.LayoutParams layoutParams = button.getLayoutParams();
layoutParams.width = widthPixels / 2;
layoutParams.height = heightPixels / 2;
button.setLayoutParams(layoutParams);
举例:资源是图片宽和长不定的一些图片,需求是宽度一定,对图片进行展示,思路就是:根据图片的宽高比,以及效果图的高度,来设置ImageView控件的长度。
4. demins 基于px和dp适配的缺点
- dimen 基于px:
宽和高都经过百分比的计算得到对应的值,通过手机分辨率进行适配,个人看来存在的问题是:
第一,Android不同分辨率的手机实在太多了,可能你说主流就可以,的确小公司主流就可以,淘宝这种App肯定不能只适配主流手机。
第二,控件在设计图上显示的大小以及控件之间的间隙在小分辨率和大分辨率手机上天壤之别,你会发现大屏幕手机上控件超级大。可能你会觉得正常,毕竟分辨率不同。但实际效果大的有些夸张。
第三,设计图(比如360640)上的内容占据屏幕的2/3,按照这种适配,特长手机(29601440 S8)上的内容也会占据2/3,这肯定不合理,控件之间的间隙会特别大,一看就不符合设计效果,手机长,内容占据低于2/3才正常,比如可能占据1/3.第四,占据资源大:好几百KB,甚至多达1M或跟多。 - dimen 基于dp:
这种适配依据的是最小宽度限定符。个人看来存在的问题是:
第一,Android 私人订制的原因,宽度方面参差不齐,不可能适配所有的手机。
第二,sp和dp值有些值不全(这个应该是可以解决的),姑且算是一个小问题。
第三,项目中增加了N个文件夹,上拉下拉查看文件非常不方便:想看string或者color资源文件需要拉很多再能到达。
第四,通过宽度限定符就近查找的原理,可以看出来匹配出来的大小不够准确。
5. 推荐今日头条适配:
- 现状:
UI设计图是按屏幕宽度为360dp来设计的,那么在上述设备上,屏幕宽度其实为1080/(440/160)=392.7dp,也就是屏幕是比设计图要宽的。这种情况下, 即使使用dp也是无法在不同设备上显示为同样效果的。 同时还存在部分设备屏幕宽度不足360dp,这时就会导致按360dp宽度来开发实际显示不全。加上16:9、4:3甚至其他宽高比层出不穷,宽高比不同,显示完全一致就不可能了,不信你去看看pixel 2的density就明白了。 - 解决方案:
通常下,我们只需要以宽或高一个维度去适配,比如我们Feed是上下滑动的,只需要保证在所有设备中宽的维度上显示一致即可,再比如一个不支持上下滑动的页面,那么需要保证在高这个维度上都显示一致,尤其不能存在某些设备上显示不全的情况。 - 实质:
① 支持以宽或者高一个维度去适配,保持该维度上和设计图一致;
② 支持dp和sp单位,控制迁移成本到最小。 - 具体代码:
px = dp * density dp是360dp,想要px正好是屏幕宽度的话,只能修改density。
/**
* 适配:修改设备密度
*/
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 targetScaleDensity = targetDensity * (sNoncompatScaledDensity / sNoncompatDensity);
int targetDensityDpi = (int) (160 * targetDensity);
appDisplayMetrics.density = targetDensity;
appDisplayMetrics.scaledDensity = targetScaleDensity;
appDisplayMetrics.densityDpi = targetDensityDpi;
final DisplayMetrics activityDisplayMetrics = activity.getResources().getDisplayMetrics();
activityDisplayMetrics.density = targetDensity;
activityDisplayMetrics.scaledDensity = targetScaleDensity;
activityDisplayMetrics.densityDpi = targetDensityDpi;
}
DisplayUtil.setCustomDensity(this, getApplication());
说明:今日头条的这种适配,只需要在baseActivity中添加一句话即可。适配就是这么简单。
-
适配效果:
说明:虽然图片是今日头条博客里面的,但是我也是亲身测过后才推荐大家的,至于没有发实测图片,公司项目多有不便,还请理解。
6. 后续
如果大家喜欢这篇文章,欢迎点赞;如果想看更多移动端方面的技术,欢迎关注!