1 参考链接
首先需要明白一些概念,比如什么是 px,什么是 dp,什么是 dpi,讲这些概念的文章很多,推荐一个:
http://www.jianshu.com/p/ec5a1a30694b
2 为什么要适配
Google 官方推荐在写布局文件的时候使用 dp,在分辨率相差不大的情况下,dp 确实能自己进行一定适配,但是如果分辨率比较大的时候,还是会有问题,所以仍然需要适配。而且如果不是大公司的话,UI 设计图通常都是以 iOS 为标准分辨率的,这让我们 Android 写起布局来还是挺不顺手的。对于适配这个问题,网上众说纷纭,以前虽然有零零散散的看过,但是没有实践过,也是在最近遇到这个需求的情况下才研究了一下,现在做个记录。
2.1 情况一
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<ImageView
android:layout_width="200dp"
android:layout_height="200dp"
android:layout_marginLeft="150dp"
android:layout_marginStart="150dp"
android:layout_marginTop="250dp"
android:src="@mipmap/ic_launcher"/>
</RelativeLayout>
上面是随便写的一个布局,就在 RelativeLayout 中放了一个 ImageView,ImageView 左边和顶部给了一定 margin,运行一下看看效果:
从左到右分别是 800x480、1920x1080、2560x1440 的分辨率的手机上的效果,可以从 ImageView 右边和底部与手机屏幕的距离很明显的看出来差异。
2.2 情况二
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<HorizontalScrollView
android:id="@+id/horizontal_scroll_view"
android:layout_width="wrap_content"
android:layout_height="50dp">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:orientation="horizontal">
<TextView
android:layout_width="90dp"
android:layout_height="match_parent"
android:background="#FFFF0000"
android:gravity="center"
android:text="第一个"
android:textColor="#FF000000"
android:textSize="18sp"/>
<TextView
android:layout_width="90dp"
android:layout_height="match_parent"
android:background="#FFFFFF00"
android:gravity="center"
android:text="第二个"
android:textColor="#FF000000"
android:textSize="18sp"/>
<TextView
android:layout_width="90dp"
android:layout_height="match_parent"
android:background="#FF00FF00"
android:gravity="center"
android:text="第三个"
android:textColor="#FF000000"
android:textSize="18sp"/>
<TextView
android:layout_width="90dp"
android:layout_height="match_parent"
android:background="#FF00FFFF"
android:gravity="center"
android:text="第四个"
android:textColor="#FF000000"
android:textSize="18sp"/>
</LinearLayout>
</HorizontalScrollView>
</RelativeLayout>
这是在一个 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();
}
3 适配
针对上面这两种情况,我们就应该考虑适配了,适配的方式有很多,比如鸿洋大神的 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 文件内容就应该是这样:
<?pxml version="1.0" encoding="utf-8"?>
<resources>
<dimen name="px1">1px</dimen>
<dimen name="px2">2px</dimen>
<dimen name="px3">3px</dimen>
<dimen name="px4">4px</dimen>
......
<dimen name="px1397">1397px</dimen>
<dimen name="px1398">1398px</dimen>
<dimen name="px1399">1399px</dimen>
<dimen name="px1400">1400px</dimen>
</resources>
values-1280x720 文件夹下面的 dimens.xml 文件中的内容就应该是这样的:
<?pxml version="1.0" encoding="utf-8"?>
<resources>
<dimen name="px1">2.25px</dimen>
<dimen name="px2">4.5px</dimen>
<dimen name="px3">6.75px</dimen>
<dimen name="px4">9px</dimen>
......
<dimen name="px1397">3143.25px</dimen>
<dimen name="px1398">3145.5px</dimen>
<dimen name="px1399">3147.75px</dimen>
<dimen name="px1400">3150px</dimen>
</resources>
values-1920x1080 文件夹下面的 dimens.xml 文件中的内容就应该是这样的:
<?pxml version="1.0" encoding="utf-8"?>
<resources>
<dimen name="px1">3.375px</dimen>
<dimen name="px2">6.75px</dimen>
<dimen name="px3">10.125px</dimen>
<dimen name="px4">13.5px</dimen>
......
<dimen name="px1397">4714.875px</dimen>
<dimen name="px1398">4718.25px</dimen>
<dimen name="px1399">4721.625px</dimen>
<dimen name="px1400">4725px</dimen>
</resources>
你没看错,就是 px,这样我们可以直接用 px 来写布局文件了,而不用对着设计图将 px换算成 dp 了。而且既然可以将 480x320 的分辨率作为基准,那么同样也可以以我们的设计图的分辨率作为基准,比如我拿到的设计图是 720x1080 分辨率的,那么我们就可以以 1280x720 作为基准,480x320 的等比例缩小:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<dimen name="px1">0.4444444px</dimen>
<dimen name="px2">0.8888889px</dimen>
<dimen name="px3">1.333333px</dimen>
<dimen name="px4">1.777778px</dimen>
......
<dimen name="px1397">620.8889px</dimen>
<dimen name="px1398">621.3333px</dimen>
<dimen name="px1399">621.7778px</dimen>
<dimen name="px1400">622.2222px</dimen>
</resources>
1920x1080 等比例扩大:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<dimen name="px1">1.5px</dimen>
<dimen name="px2">3px</dimen>
<dimen name="px3">4.5px</dimen>
<dimen name="px4">6px</dimen>
......
<dimen name="px1397">2095.5px</dimen>
<dimen name="px1398">2097px</dimen>
<dimen name="px1399">2098.5px</dimen>
<dimen name="px1400">2100px</dimen>
</resources>
这样适配后,再来对上面两种情况修改布局(以 720x1280 为基准分辨率,在 720x1280 分辨率下,1dp=2px):
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<ImageView
android:id="@+id/image_view"
android:layout_width="@dimen/px400"
android:layout_height="@dimen/px400"
android:layout_marginLeft="@dimen/px300"
android:layout_marginStart="@dimen/px300"
android:layout_marginTop="@dimen/px500"
android:src="@mipmap/ic_launcher"/>
</RelativeLayout>
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<HorizontalScrollView
android:id="@+id/horizontal_scroll_view"
android:layout_width="wrap_content"
android:layout_height="@dimen/px100">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:orientation="horizontal">
<TextView
android:layout_width="@dimen/px180"
android:layout_height="match_parent"
android:background="#FFFF0000"
android:gravity="center"
android:text="第一个"
android:textColor="#FF000000"
android:textSize="18sp"/>
<TextView
android:layout_width="@dimen/px180"
android:layout_height="match_parent"
android:background="#FFFFFF00"
android:gravity="center"
android:text="第二个"
android:textColor="#FF000000"
android:textSize="18sp"/>
<TextView
android:layout_width="@dimen/px180"
android:layout_height="match_parent"
android:background="#FF00FF00"
android:gravity="center"
android:text="第三个"
android:textColor="#FF000000"
android:textSize="18sp"/>
<TextView
android:layout_width="@dimen/px180"
android:layout_height="match_parent"
android:background="#FF00FFFF"
android:gravity="center"
android:text="第四个"
android:textColor="#FF000000"
android:textSize="18sp"/>
</LinearLayout>
</HorizontalScrollView>
</RelativeLayout>
4 特殊适配
在上面的 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 就是为了尽可能的适配有虚拟按键栏的手机。
5 Dimens 文件生成器
但是这么多 dimens 文件不可能手写,推荐一个 dimens 文件自动生成器,已上传资源:
Dimens 文件生成器
使用方法:
其中 Dpi 一定要选择 160,最后生成的文件的值后面是 dp,全局替换成 px 就好。如果是以 iPhone6 的设计图 750x1334 为基准分辨率的话,比例会有点误差 Height 要写成1333.3333。
6 总结
屏幕适配其实说麻烦也麻烦,说简单也简单,麻烦是因为文件太多,简单是因为步骤固定,这也就是我为什么选择用 dimens 适配的原因,生成一下 dimens 文件的麻烦程度我还是可以接受的,只要将正确的 dimens 文件放到工程中,后面也就不用考虑其他的了。