Android屏幕自动适配

1、背景

在Android开发中,碎片化比较严重,不同机器设备,有千奇百怪的分辨率,而如果想要我们的UI,在不同分辨率的设备上显示比例一致的话,就要去适配不同的分辨率。所以就出现了多种UI尺寸的适配方案。

2、基本概念

px: 像素、也叫图像元素,作为图像构成的基本单元。
dpi: 像素密度,指屏幕上每英寸中有多少个px。(例如:有一个屏幕分辨率为320px * 240px ,屏幕长为2英寸,宽为1.5英寸,那它的dpi = 320 / 2 = 240/1.5 = 160),它往往是写在系统出厂配置文件的一个固定值。
dp: 设备独立像素,同一个单位在不同设备上有不同的显示效果。
density:密度,简单理解为,1dp里占了多少个px。1个dp大约等于在160 dpi(mdpi)屏幕上的1个px,这被视为基准密度。

3、换算公式

density = dpi / 160;(由于dpi每台机器有一个固定值,所以density也是固定的)
px = density * dp;
px = dp * (dpi / 160);
dp = px / (dpi / 160);
dp = px / density
可以得出结论:屏幕的总 dp 宽度(高度) = 屏幕的总 px 宽度(高度) / density

4、为什么不同分辨率的设备,去显示同一个UI时,会显示不一样?

举个栗子:
设备 1,屏幕宽度为 1080px,480DPI,屏幕总 dp 宽度为: 1080 / (480 / 160) = 360dp,
设备 2,屏幕宽度为 1440px,560DPI,屏幕总 dp 宽度为: 1440 / (560 / 160) = 411dp,
可以看到屏幕的总dp宽度在不同设备上是会变化的,但是我们在Android布局文件中,写的控件的dp值是固定的,这就会导致同一个控件,在不同设备上,显示比例大小不一样。

再举个栗子:
假设我们布局中有一个 View 的宽度为 100dp,
在设备 1 中 该 View 的宽度占整个屏幕宽度的 27.8% (100 / 360 = 0.278);
在设备 2 中该 View 的宽度就只能占整个屏幕宽度的 24.3% (100 / 411 = 0.243);
可以看到这个 View 在像素越高的屏幕上,我们xml文件里设置的dp 值虽然没变,但是在屏幕的实际比例却发生了较大的变化,所以肉眼的观看效果,会越来越小,这就导致了传统的填写 dp 的屏幕适配方式产生了较大的误差。

所以,如果我们要适配的话,就得让这个View在任何分辨率的屏幕上,占屏幕的比例都是要相同的

5、今日头条适配方案

今日头条适配方案,**默认项目中只能以高度或宽度,其中的一个作为基准,进行适配。**这句话是什么意思呢?就是说,现在设计图里有一个可以上下滑动的控件,那我只要保证这个控件在我的设备上显示的宽度跟设计图保持一致就好了,所以这时候,就选择以宽度为基准;
如果设计图里是一个不支持上下滑动的页面,那么就需要保证在高度上保持一致,就选择以高度为基准。
这里有个问题,为什么只选择宽度或者高度,其中一个来适配?
今日头条是这么说的:

“一般我们设计图都是以固定的尺寸来设计的。比如以分辨率1920px * 1080px来设计,以density为3来标注,也就是屏幕其实是640dp * 360dp,宽高比为16:9。如果我们想在所有设备上显示完全一致,其实是不现实的,因为屏幕高宽比不是固定的,4:3甚至其他宽高比层出不穷,宽高比不同,显示完全一致就不可能了”。

通常情况下,宽度适配的视觉效果较好,此时高度没有适配。那么可能出现在某些屏幕上组件显示不全,我们可以采用 ScrollView 使内容可以上下滑动显示, 这样显示更加合理。

在android系统源码里,有一个把我们布局文件中控件的dp单位转换为px单位的地方,我们常用的 px 转 dp 的公式 dp = px / density,就是根据上面的方法得来的,density 在公式的运算中扮演着至关重要的一步。

public static float applyDimension(int unit, float value,
                                       DisplayMetrics metrics)
    {
        switch (unit) {
        case COMPLEX_UNIT_PX:
            return value;
        case COMPLEX_UNIT_DIP:
            return value * metrics.density;
        case COMPLEX_UNIT_SP:
            return value * metrics.scaledDensity;
        case COMPLEX_UNIT_PT:
            return value * metrics.xdpi * (1.0f/72);
        case COMPLEX_UNIT_IN:
            return value * metrics.xdpi;
        case COMPLEX_UNIT_MM:
            return value * metrics.xdpi * (1.0f/25.4f);
        }
        return 0;
    }

刚上面那个例子(两个不同设备),如果我们要适配的话,就得让这个View在任何分辨率的屏幕上,占屏幕的比例都是要相同的。这时我们该怎么做呢?根据不同设备去改变每个 View 的 dp 值?不现实。
我们这么想,如果每个View的dp值是固定不变的,那我们只要保证每一台设备的屏幕总dp宽度(高度)跟设计图的屏幕总dp宽度(高度)一样的话,就能保证每一个View在所有分辨率的屏幕上与屏幕的比例保持不变,完成等比例适配。

理解完上面这段话后,我们来看这条公式:

屏幕的总 dp 宽度 = 当前设备屏幕总宽度(单位为px) / density

在这条公式中,由于当前设备屏幕总宽度(高度)每一台设备是不一致的,所以这个值肯定是会变化的,前面我们说过,density 在每个设备上都是固定的,一个变化,一个固定,所以导致了不同设备,不同的屏幕总dp宽度(高度),前面我们说过,为了完成等比例适配,我们就要保证当前设备屏幕总宽度(高度) 要和 设计图总宽度(高度) 保持一致,做法就是去改变这个density 。

今日头条的推出一条公式:

density =当前设备屏幕总宽度(单位为px)/ 设计图总宽度(单位为 dp)

对比:

屏幕的总 dp 宽度 = 当前设备屏幕总宽度(单位为px) / density

这个公式就是把上面公式中的 屏幕的总 dp 宽度 换成 设计图总宽度,原理都是一样的,只要 density 根据不同的设备进行实时计算并作出改变,保证当前设备屏幕总宽度(高度) 要和 设计图总宽度(高度) 保持一致,也就完成了适配。

头条给出的范例:
在这里插入图片描述
现在假设设计图总宽度为 375 dp,一个 View 在这个设计图上的尺寸是 50dp * 50dp,这个 View 的宽度占整个设计图宽度的 13.3% (50 / 375 = 0.133),那我们就来验证下在使用今日头条屏幕适配方案的情况下,这个 View 与屏幕宽度的比例在分辨率不同的设备上是否还能保持和设计图中的比例一致?

验证设备1

屏幕总宽度为 1080 px,根据今日头条的的公式求出 density,1080 / 375 = 2.88 (density)
这个 50dp * 50dp 的 View,系统最后会将高宽都换算成 px,50dp * 2.88 = 144 px (根据公式 dp * density = px)
144 / 1080 = 0.133,View 实际宽度与 屏幕总宽度 的比例和 View 在设计图中的比例一致 (50 / 375 = 0.133),所以完成了等比例缩放。

验证设备2

屏幕总宽度为 1440 px,根据今日头条的的公式求出 density,1440 / 375 = 3.84 (density)
这个 50dp * 50dp 的 View,系统最后会将高宽都换算成 px,50dp * 3.84 = 192 px (根据公式 dp * density = px)
192 / 1440 = 0.133,View 实际宽度与 屏幕总宽度 的比例和 View 在设计图中的比例一致 (50 / 375 = 0.133),所以也完成了等比例缩放。

这就是今日头条适配方案的总体思想。

6、第三方库应用–AndroidAutoSize

它是在吸收今日头条适配方案的总体思想的基础上,完成的一个第三方库。
在 build.gradle 中引入

implementation 'me.jessyan:autosize:1.2.1'

在 AndroidManifest.xml 中设定设计图纸中使用的屏幕大小(不一定是dp,还可以设置sp、pt、in、mm 所有的单位都能支持,不支持 px)。

<application>
        <meta-data
            android:name="design_width_in_dp"
            android:value="360"/>
        <meta-data
            android:name="design_height_in_dp"
            android:value="780"/>
</application>

这样代码烧进设备后,就可以自动完成适配了。

有一个问题,为什么在 AndroidManifest.xml 加上设计图的宽度、高度后,设备就能自己适配呢?

这是因为AndroidAutoSize这个第三方库,它内部有一个ContentProvider,在ContentProvider的onCreate方法里完成了初始化(也是在这里拿到了 AndroidManifest.xml 里设计图的宽度、高度),由ContentProvider的onCreate发生在Application的onCreate方法之前,所以我们在使用这个第三方库时,不用我们人为的去初始化它,因为它在应用启动时候,就已经自动也完成了初始化。

├── ActivityThread.handleBindApplication
    ├── LoadedApk.makeApplication
        ├── Instrumentation.newApplication
            ├── Instrumentation.newApplication
                ├── (Application)clazz.newInstance() //调用构造方法
                ├── Application.attach
                    ├── Application.attachBaseContext //调用attachBaseContext方法
    ├── ActivityThread.installContentProviders
        ├── ActivityThread.installProvider
            ├── ContentProvider.attachInfo
                ├── ContentProvider.attachInfo
                    ├── ContentProvider.onCreate  // 调用 ContentProvider的onCreate方法
    ├── Instrumentation.callApplicationOnCreate
        ├── Application.onCreate  //调用Application的onCreate方法
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值