Android 屏幕适配总结

Posted Eli Shaw

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android 屏幕适配总结相关的知识,希望对你有一定的参考价值。

本文主要涉及以下几篇文章

Android 屏幕适配总结

Android ConstraintLayout 使用与适配(使用篇)

Android ConstraintLayout 使用与适配(适配篇)

Android 自动创建最小宽度限定符文件插件 AutoDimens

目录

一、与屏幕相关的概念

1. 屏幕尺寸

2.屏幕分辨率

3. 屏幕像素密度(dpi)

二、为什么需要适配

1.屏幕比例不同(屏幕尺寸不一样)

2.屏幕像素密度(dpi)不同

三、Android 屏幕适配原因

四、官方推荐方式 —— dp

具体分析

五、dp + 修改 density 值适配

一、在 Application 中修改 density 适配

二、在 Activity 中修改 density 适配

三、使用 dp + 修改 density 适配的弊端

六、官方推荐方式 —— 最小宽度限定符适配

一、最小宽度

二、限定符

三、原理解析

四、使用

七、官方推荐方式 —— ConstraintLayout

八、总结

1.建议

2.各适配方式优缺点


android 适配相关的文章有很多,其中也有不少好文章,对我的受益很多,可还是有些点不是很明白,因此自己亲自验证并总结成本文。

一、与屏幕相关的概念

1. 屏幕尺寸

屏幕大小单位(如平常说的 55寸电视、5寸手机),统一度量单位 (英寸),测量方式是屏幕对角线的长度为几英寸就是几寸屏。

1英寸=2.54厘米(cm)

2.屏幕分辨率

屏幕横向、竖向显示的像素数。如分辨率为1080X1920 的屏幕,即为横向1080个像素点,竖向1920个像素点的屏幕。

3. 屏幕像素密度(dpi)

屏幕每英寸显示的像素点数,在同一尺寸下,分辨率越高,屏幕越清晰,dpi越高。得到屏幕像素密度的计算方式如下

二、为什么需要适配

我看到的文章大部分是说 Android 屏幕碎片化严重所以需要适配。可碎片化只是一种客观存在的现象。而 Android 之所以需要屏幕适配的主要原因有两个:

1.屏幕比例不同(屏幕尺寸不一样)

屏幕比例不同,即屏幕宽高比不同,(就是宽高的不同)。屏幕比有很多,如:4:3、5:4、16:10、16:9 等。如在同密度、同尺寸的情况下,屏幕比不同会出现宽高不一至的情况,如下图示例

在同样的5.5寸屏,宽高比分别是 16:9,7:6,最终两个屏幕的大小不一样,这样画一条360px的直线,在图3就是满屏,在图4就只占屏幕的四分之三,而同样的内容可以在图3中显示下,在图4中就有可能超过底部边缘显示不下。这在一些情况下就需要适配屏幕以保证与设计图一致。

2.屏幕像素密度(dpi)不同

屏幕密度不同,即设备分辨率的不同,如在同尺寸的屏幕中,不同分辨率会使屏幕密度不同。如下图示例

在上面两个图中,屏幕的尺寸、比例,都是一样的,但是分辨率是不一样的,这就造成屏幕密度不一样。这样画一条 360px 的直线,在图5中是满屏,在图6中就只有三分之一,在图6中可以显示的内容,在图5中就有可能超过底部边缘,显示不下。这种情况也是造成需要适配的原因。

三、Android 屏幕适配原因

总结一下适配的 2 个主要原因

1.屏幕像素密度不一样

2.屏幕比例不同(屏幕尺寸不一样)

其实适配,主要是针对这两点进行适配的。理解了屏幕的概念,知道了适配屏幕的原因,这样处理问题就有了方向。下面说说几种官方推荐的主流适配方式,与民间使用比较多的适配方式。

四、官方推荐方式 —— dp

dp 是 Android 里的一个与像素密度无关的单位,因此 Google 推荐使用 dp 解决屏幕像素密度不一样的场景,就是上面所说的第 1 个原因。

dp 为什么可以解决这个像素密度引起的适配问题呢?是因为程序运行后,Android 会根据当前屏幕密度(dpi)将布局文件中的 dp 值转换为 px 值显示在屏幕上。调整公式是:px = dp * (dpi / 160)。

总结:Android 默认会将屏幕密度为 160dpi 的屏幕定为基准(注意:与屏幕分辨、屏幕尺寸没有任何联系,此处不要与其他两个概念联系起来,他们之间没有任何的影响)。这是什么意思呢? 也就是说我们在布局文件中写的 12dp、13dp、等以 dp 为单位的值。Android 默认此布局文件是一个运行在屏幕像素密度为 160dpi 屏幕的布局文件。如果真实运行后屏幕像素密度不是 160 dpi 的屏幕,此时 Android 会以 160 dpi 为基准,将布局文件中 dp 单位的尺寸调整大小,而这个调整的公式就是:px = dp * (dpi / 160)。

这一点我们可以用事实证明一下,在同一分辨率 (360X640),不同像素密度 160 dpi 和 280dpi 的屏幕下,分别画一条 120px 和一条 120dp 的直线,效果如下:

图1:分辨率 360X640,像素密度 160dpi

 

图2:分辨率 360X640,像素密度 280dpi

首先看以 dp 为单位的使用结果

    图1 中可以证明在像素密度为 160 dpi 的屏幕下,px 单位与 dp 单位是一致的,即 1dp = 1px (说明:这与屏幕分辨率无关,就算是在 1080*1920的屏幕下,只要像素密度是 160dpi,那 1dp 就等于 1px)。

    而在像素密度为 280 dpi 的屏幕中 120dp 的直线转换为 px,利用公式:px = 120dp * (280dpi / 160),实际像素是 210px。这就是 图2 中 120dp 实际显示的像素数。

再来看以 px 为单位的使用结果

    图1、图2中,在不同像素密度的屏幕下,使用 px 为单位的直线,显示结果居然是一样的(这是句费话),px 本身与像素密度是无关的,在同样大的分辨率(360X640)屏幕下,使用 px 为单位,显示结果当然是一样的。

具体分析

到此就会产生一种疑惑,不是说 dp 可以解决像素密度不同引起的适配问题吗?为什么在这里还不如 px 呢?这是因为这种示例是一种特殊情况,在广大的手机厂商与海洋般的Android手机机型中,要想使所有手机是同一个分辨率,那是不可能的。一但在不同分辨率下,使用 px 为单位,可想而知,在 360X480 的屏幕 120px 的直线占屏幕宽的 1/3,而在 600X840 的屏幕下就是屏幕宽的 1/5,显示效果立马不同。

而 dp 在众多的机型面前,并不能"完美"按照设计图的样式进行适配,可为什么官方还要推荐呢?我想是因为他可以做到一套标准的适配,什么意思呢?假如你的设计图分辨率是 360x640,DPI 是160 的。则宽、高、DPI 比是 9:16:4,现在只要是以这个比值的屏幕都可以做到完美适配。示例如下

设计图:分辨率:360X640,DPI是:160

 布局代码

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".AutoActivity">

    <View
        android:layout_width="360dp"
        android:layout_height="22dp"
        android:layout_marginTop="30dp"
        android:background="@color/colorAuto" />

    <View
        android:layout_width="300dp"
        android:layout_height="40dp"
        android:layout_marginTop="60dp"
        android:background="@color/colorAuto" />

    <View
        android:layout_width="260dp"
        android:layout_height="64dp"
        android:layout_marginLeft="50dp"
        android:layout_marginTop="18dp"
        android:background="@color/colorAuto" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="36dp"
        android:orientation="horizontal">

        <View
            android:layout_width="100dp"
            android:layout_height="60dp"
            android:layout_marginLeft="30dp"
            android:background="@color/colorAuto" />

        <View
            android:layout_width="120dp"
            android:layout_height="86dp"
            android:layout_marginLeft="36dp"
            android:background="@color/colorAuto" />
    </LinearLayout>

    <View
        android:layout_width="280dp"
        android:layout_height="95dp"
        android:layout_marginLeft="30dp"
        android:layout_marginTop="184dp"
        android:background="@color/colorAuto" />
</LinearLayout>

此布局分别运行在分辨率:540X960,DPI是240 和 1080X1920,DPI是480的屏幕上适配效果是完全一致的,因为他们的宽、高、DPI比值都是 9:16:4。这一点可以用PS验证,设计图中的矩形是黑色,540X960屏幕布局中的矩形设置为粉色,1080X1920 屏幕布局中的矩形设置为绿色。用PS将两种图片重叠,看是不是百分百对应。结果如下

这种在同比例情况下的适配是完全吻合的,可一但宽高DPI比有一个不同,适配就会产生偏差,如设置一个宽高同样是1080X1920,但DPI为360的屏幕,它的宽高DPI比值就是 9:16:3。与设计图对比结果如下

分辨率1080x1920 DPI 360 与设计图重叠比对

橙色是运行在屏幕为 1080X1920 DPI360 上的截图,黑色是原设计图,两张图重叠比对可以看到差别很大,所以说单纯的使用 dp 并不能解决适配问题。

五、dp + 修改 density 值适配

民间有一种使用 dp + 修改 density 值的适配方式,也可以近乎完美的适配屏幕。

上边说过只要保持当前设备屏幕的 宽、高、dpi 比值 设计图的 宽、高、dpi 比值 一致 就可以完美适配,设备的宽、高是无法修改的,但是 dpi 是有办法修改的。不过这种方式只能以设备的宽或高其中一种方式进行屏幕适配,因为设备的宽高是硬性条件无法修改。因此大多数情况是以设备屏幕的宽进行适配的。例如上面例子中的设备 分辨率是1080X1920 dpi为360 屏幕的 宽与dpi 比是 3:1,而 设计图的 宽与dpi 比是 9:4,要使 设备宽、dpi 比值 设计图宽、dpi 比值一致,就要修改设备的 dpi 值,计算公式如下:

        densityDpi值 = 设备实际宽 / 设计图宽 x 设计图dpi(默认为 160,根据实际情况调整)

还有一种直接修改 density 值也可使设备与设计图比值一致,

        density值设备实际宽 / 设计图宽

修改这两个其中的任何一个,都可以实现不同屏幕的适配。

修改 density 的代码位于 Context 的 getResources() 中,而 Application 和 Activity 都是 Context 的子类,因此这两个类中都可以修改设备的 density 值进行屏幕适配。

一、在 Application 中修改 density 适配

注意:此方式有 Android 版本限制,在 5.0 前与 8.0 后无法使用

public class App extends Application 

    private static final int designWidth = 360; //设计图宽

    @Override
    public void onCreate() 
        super.onCreate();
        //在有些设备程序运行时无法自动调用 getResources 方法,可在此主动调用一次
//        getResources();
    

    @Override
    public Resources getResources() 

        int[] screenSize = getScreenSize(this);
        int screenWidth = screenSize[0]; //当前设备屏幕宽
        float targetDensity = screenWidth * 1.0f / designWidth; //当前设备屏幕宽与设计图宽度比值
        int targetDensityDpi = (int) (targetDensity * 160); //根据设备与设计图宽度比值算出当前屏幕适配后的DPI

        Resources resources = super.getResources();

        /**
         * 修改屏幕密度有两种方法 
         * 一种使用 Configuration
         * 另一种使用 DisplayMetrics
         * 这两种方法任何一个都可以使用
         */

        //使用 Configuration 修改设备密度
        Configuration configuration = resources.getConfiguration();
        configuration.densityDpi = targetDensityDpi; //设置当前程序的DPI用于屏幕适配
        resources.updateConfiguration(configuration, resources.getDisplayMetrics());

        //使用 DisplayMetrics 修改设备密度
//        DisplayMetrics displayMetrics = resources.getDisplayMetrics();
//        displayMetrics.density = targetDensity;
//        displayMetrics.densityDpi = targetDensityDpi;
        return resources;
    

    /**
     * 获取屏幕信息
     */
    private int[] getScreenSize(Context context) 
        int[] size = new int[2];

        WindowManager w = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        Display d = w.getDefaultDisplay();
        DisplayMetrics metrics = new DisplayMetrics();
        d.getMetrics(metrics);

        size[0] = metrics.widthPixels;
        size[1] = metrics.heightPixels;
        return size;
    

二、在 Activity 中修改 density 适配

在 Application 中修改 density 的方式在 Android 5.0前 和 Android 8.0 后无法使用。而另一种方式没有版本限制,即在 Activity 中的 setContentView() 前设置 Density,代码如下

1.屏幕适配工具类

public class ScreenUtil 

    private static int designWidth = 360; //设计图宽

    public static void setDensity(Context context) 
        //计算适配后的 density 值
        int[] screenSize = getScreenSize(context);
        int screenWidth = screenSize[0]; //当前设备屏幕宽
        float targetDensity = screenWidth * 1.0f / designWidth; //当前设备屏幕宽与设计图宽度比值
        int targetDensityDpi = (int) (targetDensity * 160); //根据设备与设计图宽度比值算出当前屏幕适配后的DPI

        //获取 Context 的 Resources
        Resources resources = context.getResources();

        //使用 Configuration 修改 density
        Configuration configuration = resources.getConfiguration();
        configuration.densityDpi = targetDensityDpi; //设置当前程序的DPI用于屏幕适配
        configuration.fontScale = 1f; //适配字体
        resources.updateConfiguration(configuration, resources.getDisplayMetrics());

        //使用 DisplayMetrics 修改 density
//        DisplayMetrics displayMetrics = resources.getDisplayMetrics();
//        displayMetrics.density = targetDensity;
//        displayMetrics.densityDpi = targetDensityDpi;
    

    public static int[] getScreenSize(Context context) 
        int[] size = new int[2];

        WindowManager w = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        Display d = w.getDefaultDisplay();
        DisplayMetrics metrics = new DisplayMetrics();
        d.getMetrics(metrics);

        size[0] = metrics.widthPixels;
        size[1] = metrics.heightPixels;
        return size;
    

2.在 Activity 中进行适配

public class AutoJavaActivity extends AppCompatActivity 

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
        //修改 Density 进行屏幕适配,此方法必需在 setContentView 前执行
        ScreenUtil.setDensity(this);
        setContentView(R.layout.activity_auto_base);
    

  以上两种适配后的截图与设计图对比结果如下

分辨率1080x1920 DPI 360 修改dpi后与设计图重叠比对

修改 Density 进行适配的方式,不需要每个 Activity 都执行适配代码,在整个程序运行期间,只要在入口 Activity 修改一次进行适配就好,其他的 Activity 无须再次修改 Density 值。

三、使用 dp + 修改 density 适配的弊端

1.设计图尺寸无法修改 (注:修改设计图尺寸的情况很少见,在此分析原理与解决方式)

如使用 360X640 做设计图进行开发,一但使用这个尺寸做基准适配,以后设计图必须都要使用这个尺寸做设计,一但设计图的尺寸变更,以前布局文件的所有尺寸值都要按新设计图基准进行修改。或者将新尺寸的设计图换算成原设计图 360X640,换算方式

        设计图差值 = 新设计图宽 / 原设计图宽   或   新设计图高 / 原设计图高

        新设计图换算后值 = 实际尺寸 / 差值

如:新设计图尺寸是 720X1280

        差值 = 720 / 360 = 2

        新设计图中一条36dp的线换算后 = 36 / 2 = 18dp

以后都要按这种方式将新设计图中的每个数值除以 2 使用才能与之前的设计图适配对接。

2.不建议将设计图基准尺寸值放在 AndroidManifest.xml 清单文件中。也不建议使用 Application.ActivityLifecycleCallbacks 监听 Activity 生命周期,将 ScreenUtil.setDensity(this); 放在此监听类中进行适配

如果你能确保无论任何时候你的工程不会提供给其他应用做 libary 使用,此建议可省略。否则要重视这个建议,因为有可能其他应用无法使用,原因1清单文件中相同 key 无法合并,原因2 Application 中监听 Activity 生命周期最终只能设置一种基准尺寸的适配。

这个问题根设计图尺寸无法修改类似,如果有多个三方库都使用这样的适配方式,并且多个三方库的设计图尺寸不一样,如一个是 360X640,一个是 750X1280, 一个是 460X720,如果你真遇到这样的情况,恭喜,适配基本无法进行。除非你能要求三方库改设计图尺寸,并且要三方库反工,把以前的布局按你们自己设计图尺寸修改。随着项目越来越大,功能越来越多,集成专业领域三方库的概率也是很大的。所以在选择适配方式时应该考虑这个因素。

3.高分辨率屏幕应该显示更多内容,而不是千篇一律

这种强制将屏幕设置同一比例的方式,使所有屏幕显示效果一模一样,高分辨率屏幕与低分辨率屏幕显示内容一样,高分辨率的存在变得尴尬,用户为什么要多花钱买高分屏呢???

4.宽高无法兼备

这种方式只能以宽或高一种标准来适配,如果对宽高都有要求的场景,将不能使用这种方式适配。

例如还是使用上边的360X640 的设计图,使用此方式适配,在屏幕分辨率为 720X1200 DPI 320 的屏幕下是有问题的,结果如下

很明显在 图2 分辨率为 720X1200 DPI 320 的屏幕下,底部的View 超过了屏幕边缘,这样的场景就不适合使用这样的方式适配,如果需要宽高兼备的适配,只能使用百分比布局 Android ConstraintLayout

5.非官方推荐

官方推出了多种适配方式,就是没推荐这种适配方式,我想官方不主动推荐这种适配方式那肯定是有原因的,不然官方为什么舍简求繁呢?难道官方的开发人员都很傻很闲吗?这种修改 density 适配的方式如果像放在 Application 中修改 density 值一样,哪天有个 Android 版本不支持了,而你维护的项目正好使用这种方式,那你就。。。。。。

六、官方推荐方式 —— 最小宽度限定符适配

最小宽度限定符的适配方式其实是官方使用 dp 值直接适配的升级版本。

我一般把 最小宽度限定符 这段话拆分为二理解:1.最小宽度2.限定符

一、最小宽度

最小宽度是指屏幕尺寸的最小一方,如:1080 X 1920 的屏幕,最小值是 1080,则以 1080 为适配尺寸,不管屏幕的方向是横向还是竖向,都以 1080 为适配尺寸的依据。

二、限定符

限定符就是屏幕最小宽度的 dp 值。上边说过屏幕分辨率的单位是 px,而 Android  中的 dp 值最终使用一个公式将 dp 转换成 px 显示在屏幕上,公式是

    px = dp * (dpi / 160)

那么要将屏幕分辨率 px 转换成 dp 就需要变换公式 dp = px / (dpi / 160)

举例:假如 屏幕1 是一个分辨率为 1080 X 1920 DPI 为 480 的屏幕,它屏幕的最小宽度是 1080px,DPI 是 480,它的 dp 值就是 dp = 1080px / ( 480dpi / 160) = 360dp,因此 屏幕1 的限定符为 360dp。

三、原理解析

官方为什么要使用这个最小宽度限定符做适配呢?我觉得这种方式是直接使用 dp 适配的升级版本,在分析直接使用 dp 值做适配时说过 dp 可以做到一套标准的适配,即只要屏幕的 宽、高、DPI 比值一致,无论在哪个屏幕下,显示效果都是百分百对应的。然而海洋般的 Android 手机制造商中,一套标准只能适配屏幕严重碎片化的一种。因此官方使用一个笨重的方法升级了直接使用 dp 值适配的方式。可能官方想既然一套标准不够用,那我就将一套标准升级成 N 套(即让开发人员指定多个限定符)。N 套中的每一套标准,都可看成 宽 DPI 的比值。有了多个同比值,屏幕就可适配的更多。

放到 Android 工程中是不可能使用比值做标准的,因此取用最小宽度限定符做标准,针对每一个最小宽度 dp 值创建一个备用布局文件夹,这个文件夹可以是 values 的,也可以是布局 layout。格式是 values-swXXXdp 或 layout-swXXXdp 如下

这些文件夹都是自己创建的(当然有插件可以一键生成 ScreenMatch),你需要适配哪种屏幕的最小宽度 dp 值,就创建哪个。Android 计算出当前屏幕的最小限定符值去寻找相应文件夹下的布局使用,如果当前屏幕的最小限定符值没有在工程目录中找到,会使用与此值最相近的限定符文件夹中的布局。

当然使用最多的还是 values 文件夹,毕竟适配多个 layout 太费劲了,不过有特殊需求的可以使用。

四、使用

知道了原理,现在说说怎么使用,你首先需要确定一个最小宽度限定符的基准值,一般就是设计图的尺寸,如你的设计图尺寸是 360X640,那么 360 就是最小宽度限定符的基准值,因为设计图没有 dpi 的概念,因此设计图尺寸 px 与 dp 的关系是 1:1,即设计图尺寸是多少 px,就是多少 dp。

有了最小宽度限定符基准值 360,就以此基准调整各最小宽度限定符布局中的数值,调整公式是

    当前最小宽值基准值实际尺寸值

举例:

假如指定 360 为最小宽度限定符基准值,那么最小宽度限定符 values-sw360dp 文件中的值 1dp、2dp、3dp

转换到限定符为 480 的文件夹中, 1dp 就换算成了 480 / 360 * 1dp = 1.3333dp,如下

这样程序运行时计算出当前屏幕最小宽度限定符值,去相应限定符文件 dimens.xml 中寻找指定的值,就可达到适配效果。

七、官方推荐方式 —— ConstraintLayout

见:Android ConstraintLayout 使用与适配(适配篇)

八、总结

1.建议

无论使用哪种适配方式,都建议将所有布局页面中的数值 dp、sp、px 等等放在 dimens.xml 文件中,好处就是容易更改其他适配方式。如使用 dp + 修改 density 值适配,哪天这种方式不能用了,转最小宽度限定符适配会很容易,借助工具直接生成相应限定符文件就可以。如果数值写死在每个布局文件中,改起来会相当可观。

2.各适配方式优缺点

1.单独使用 dp 值的使用场景

    如果说只想适配市面上的主流机,在主流机型的不同屏幕下,对页面控件的尺寸存在一点误差能够接受。则完全可以只使用 dp 值适配。因为主流机中屏幕的 宽、高、DPI 比差距不会很大。拿常见的左右边距 16dp 来说,在主流机型中不同屏幕差生的差异,普通人眼不会感觉到不适应,不能接受,但是对于专业人来说可能会有美观感的问题。

2.dp + 修改 density 值适配的使用场景

    这种方式最大的好处就是方便,并且适配效果比直接使用 dp 要好很多,但是弊端还是要考虑的,如果你能接受他的弊端,这种方式还是比较方便快捷的。

3.最小宽度限定符适配

    目前官方推荐的方式,基本不会有后遗症(弃用)的风险,而且很灵活,哪种尺寸的屏幕、哪种情况下的屏幕都可以自己定制布局显示样式。

    但是N+1个的限定符文件夹(values-XXXdp),使终是程序员中的一个心病,总感觉不够友好,不够智能。

4.ConstraintLayout 适配

    这种百分比约束布局适配,官方出的还是很得人心的,只是要弃用以前的5大布局逻辑还是需要一点时间的,并且百分比有些情况需要计算偏移量,多少不如直接写个值方便。最令人吐槽的是解决了百分比,却没有解决字体的适配。还需要搭配最小宽度限定符单独适配字体,这就有点弯弯绕了。

以上是关于Android 屏幕适配总结的主要内容,如果未能解决你的问题,请参考以下文章

Android屏幕适配总结

android开发分辨率适配总结

iOS设计尺寸375*812怎么适配安卓

android屏幕适配做哪几个尺寸

Android 屏幕适配

Android 屏幕适配总结