文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

Android 屏幕适配总结

2022-06-06 13:52

关注

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

Android 屏幕适配总结

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

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

目录

一、与屏幕相关的概念

1. 屏幕尺寸

2.屏幕分辨率

3. 屏幕像素密度(dpi)

二、为什么需要适配

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

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

三、Android 屏幕适配原因

四、官方推荐方式 —— dp

具体分析

五、dp + 修改 density 值适配

一、在 Application 中修改 density 适配

二、在 Activity 中修改 density 适配

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

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

一、最小宽度

二、限定符

三、原理解析

四、使用

七、官方推荐方式 —— ConstraintLayout

与 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/6,显示效果立马不同。

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

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

 布局代码



此布局分别运行在分辨率: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 修改设备密度
        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 使用与适配(适配篇)


作者:Eli Show


阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-移动开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯