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,运行一下看看效果:

android 使用dp会不会自适应手机 安卓dpm原因_android

      

android 使用dp会不会自适应手机 安卓dpm原因_适配_02

      

android 使用dp会不会自适应手机 安卓dpm原因_android_03

 

从左到右分别是 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,运行一下看看效果:

android 使用dp会不会自适应手机 安卓dpm原因_适配_04

     

android 使用dp会不会自适应手机 安卓dpm原因_xml_05

     

android 使用dp会不会自适应手机 安卓dpm原因_xml_06

 

从左到右仍然是 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 文件夹下,最后的文件夹结构如下:

android 使用dp会不会自适应手机 安卓dpm原因_android_07

 

每个文件夹下放置一个 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>

 

android 使用dp会不会自适应手机 安卓dpm原因_android_08

     

android 使用dp会不会自适应手机 安卓dpm原因_适配_09

     

android 使用dp会不会自适应手机 安卓dpm原因_android_10

 

<?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>

 

android 使用dp会不会自适应手机 安卓dpm原因_适配_11

     

android 使用dp会不会自适应手机 安卓dpm原因_UI_12

     

android 使用dp会不会自适应手机 安卓dpm原因_android_13

 

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 文件生成器

使用方法:

android 使用dp会不会自适应手机 安卓dpm原因_xml_14

 

其中 Dpi 一定要选择 160,最后生成的文件的值后面是 dp,全局替换成 px 就好。如果是以 iPhone6 的设计图 750x1334 为基准分辨率的话,比例会有点误差 Height 要写成1333.3333。

 

6 总结

屏幕适配其实说麻烦也麻烦,说简单也简单,麻烦是因为文件太多,简单是因为步骤固定,这也就是我为什么选择用 dimens 适配的原因,生成一下 dimens 文件的麻烦程度我还是可以接受的,只要将正确的 dimens 文件放到工程中,后面也就不用考虑其他的了。