http://blog.csdn.net/qq_34161388/article/details/73469319
首先需要明白一些概念,比如什么是 px,什么是 dp,什么是 dpi,讲这些概念的文章很多,推荐一个:
http://www.jianshu.com/p/ec5a1a30694b
Google 官方推荐在写布局文件的时候使用 dp,在分辨率相差不大的情况下,dp 确实能自己进行一定适配,但是如果分辨率比较大的时候,还是会有问题,所以仍然需要适配。而且如果不是大公司的话,UI 设计图通常都是以 iOS 为标准分辨率的,这让我们 Android 写起布局来还是挺不顺手的。对于适配这个问题,网上众说纷纭,以前虽然有零零散散的看过,但是没有实践过,也是在最近遇到这个需求的情况下才研究了一下,现在做个记录。
上面是随便写的一个布局,就在 RelativeLayout 中放了一个 ImageView,ImageView 左边和顶部给了一定 margin,运行一下看看效果:
从左到右分别是 800x480、1920x1080、2560x1440 的分辨率的手机上的效果,可以从 ImageView 右边和底部与手机屏幕的距离很明显的看出来差异。
这是在一个 HorizontalScrollView 中放置了一个 LinearLayout,里面放置了 4 个 TextView,给了固定宽度为 90dp,运行一下看看效果:
从左到右仍然是 800x480、1920x1080、2560x1440,这下区别更明显,800x480 已经装不下这 4 个 TextView 了,1920x1080 则是刚好平分,2560x1440 则还有富余。
现在我在项目中遇到的情况就是情况二,在 HorizontalScrollView 中需要放置几个 TextView,每个 TextView 的宽度需要刚好为屏幕的 1./4。其实不适配也可以做,就是在 Activity 中使用 Java 代码获取屏幕宽度,动态修改每个 TextView 的宽度。不过这样的代码,写出来还是有点恶心的:
HorizontalScrollView horizontalScrollView = findViewById(R.id.horizontal_scroll_view);
LinearLayout linearLayout = (LinearLayout) horizontalScrollView.getChildAt(0);
for (int i = 0; i < linearLayout.getChildCount(); i++) {
linearLayout.getChildAt(i).getLayoutParams().width = getResources().getDisplayMetrics().widthPixels / 4;
linearLayout.getChildAt(i).requestLayout();
}
针对上面这两种情况,我们就应该考虑适配了,适配的方式有很多,比如鸿洋大神的 AutoLayout(已经停止维护了,不建议在项目中使用),还有写多个 layout 布局呀,还有 dimens 适配呀等等,在对比了多种方法后,最后我采用了 dimens 适配这种方式。这样的适配方式虽然会加入很多不同分辨率的 dimens 文件,但是比起写多个 layout 布局,要修改 UI 的时候需要修改很多 layout 的情况,还是要方便一点,最主要的是写布局的时候会更方便!
首先我们要建立多个不同分辨率的 values 文件夹,主流的分辨率有:values-800x480、values-1280x720、values-1920x1080、values-2560x1440,而在 Android 中是以 480x320 为基准的,所以这个分辨率的 dimens 文件可以放到默认的 values 文件夹下,最后的文件夹结构如下:
每个文件夹下放置一个 dimens.xml 文件,dimens 中的值是按照基准分辨率进行等比例扩大或缩小的,如 480x320 的分辨率作为基准分辨率,那么 values 下的 dimens.xml 文件内容就应该是这样:
1px
2px
3px
4px
......
1397px
1398px
1399px
1400px
values-1280x720 文件夹下面的 dimens.xml 文件中的内容就应该是这样的:
2.25px
4.5px
6.75px
9px
......
3143.25px
3145.5px
3147.75px
3150px
values-1920x1080 文件夹下面的 dimens.xml 文件中的内容就应该是这样的:
3.375px
6.75px
10.125px
13.5px
......
4714.875px
4718.25px
4721.625px
4725px
你没看错,就是 px,这样我们可以直接用 px 来写布局文件了,而不用对着设计图将 px换算成 dp 了。而且既然可以将 480x320 的分辨率作为基准,那么同样也可以以我们的设计图的分辨率作为基准,比如我拿到的设计图是 720x1080 分辨率的,那么我们就可以以 1280x720 作为基准,480x320 的等比例缩小:
0.4444444px
0.8888889px
1.333333px
1.777778px
......
620.8889px
621.3333px
621.7778px
622.2222px
1920x1080 等比例扩大:
1.5px
3px
4.5px
6px
......
2095.5px
2097px
2098.5px
2100px
这样适配后,再来对上面两种情况修改布局(以 720x1280 为基准分辨率,在 720x1280 分辨率下,1dp=2px):
在上面的 values 文件夹结构示意图中会有那么两个奇怪的分辨率,1720x1080 和 2360x1440,这个分辨率的手机应该是没有的,那么为什么要写这么两个奇怪的分辨率呢,里面的文件每一个 px 的值应该是多少呢?
先来说一种情况。有的手机是有虚拟按键的,比如 Nexus、华为,这些有虚拟按键的手机分辨率是将虚拟按键的高度算进去了的,但是 Android 在对 values 适配的时候却不会将虚拟按键的高度算进去,比如一个手机的分辨率本来是 1920x1080,但是虚拟按键栏占了144,所以它在适配的时候并不会去找 1920x1080 这个分辨率,而是去找 values-1776-1080,要是这个分辨率没有,那么就会去找低一级的分辨率 1280x720,再没有就再找更低一级的。所以布局又会出问题了,怎么办呢?其实虚拟按键栏的高度我们是可以获取的:
/**
* Description:获取虚拟按键栏高度
* Date:2018/8/1
*/
public static int getNavigationBarHeight(Context context) {
int result = 0;
if (hasNavBar(context)) {
Resources res = context.getResources();
int resourceId = res.getIdentifier("navigation_bar_height", "dimen", "android");
if (resourceId > 0) {
result = res.getDimensionPixelSize(resourceId);
}
}
return result;
}
/**
* Description:检查是否存在虚拟按键栏
* Date:2018/8/1
*/
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
public static boolean hasNavBar(Context context) {
Resources res = context.getResources();
int resourceId = res.getIdentifier("config_showNavigationBar", "bool", "android");
if (resourceId != 0) {
boolean hasNav = res.getBoolean(resourceId);
// check override flag
String sNavBarOverride = getNavBarOverride();
if ("1".equals(sNavBarOverride)) {
hasNav = false;
} else if ("0".equals(sNavBarOverride)) {
hasNav = true;
}
return hasNav;
} else { // fallback
return !ViewConfiguration.get(context).hasPermanentMenuKey();
}
}
在得到虚拟按键栏的高度后,如为 144,再用分辨率去减这个高度,如 1920-144=1776,那么我们再增加一个 values-1776x1080 文件夹,里面的 dimens.xml 文件同 1920x1080 即可。但是不同手机的虚拟按键的高度也可能是不一样的,不可能写 values-1776x1080、values-1780x1080 这么多文件夹,上面说了找不到这个分辨率的时候,就会去找低一级的分辨率,所以我们写一个最低的就行了,比如 1720x1080,那么 1776x1080、1780x1080 等都会去找 1720x1080 了。
所以就多了 1720x1080 和 2360x1440 这两个文件夹,1720x1080 与 1920x1080 是一样的,2360x1440 则与 2560x1440 是一样的,把高写小了 200 就是为了尽可能的适配有虚拟按键栏的手机。
但是这么多 dimens 文件不可能手写,推荐一个 dimens 文件自动生成器,已上传资源:
Dimens 文件生成器
使用方法:
其中 Dpi 一定要选择 160,最后生成的文件的值后面是 dp,全局替换成 px 就好。如果是以 iPhone6 的设计图 750x1334 为基准分辨率的话,比例会有点误差 Height 要写成1333.3333。
屏幕适配其实说麻烦也麻烦,说简单也简单,麻烦是因为文件太多,简单是因为步骤固定,这也就是我为什么选择用 dimens 适配的原因,生成一下 dimens 文件的麻烦程度我还是可以接受的,只要将正确的 dimens 文件放到工程中,后面也就不用考虑其他的了。