android屏幕适配

259 阅读9分钟

一.概述

density是dp和px之间转换系数:px= density * dp.

DPI是像素密度,指的是在系统软件上指定的单位尺寸的像素数量,它往往是写在系统出厂配置文件的一个固定值。根据公式:density = DPI/160 每个设备的density也是固定的(厂商写入的)。

不同设备,density和屏幕没有任何关系(相同的density屏幕宽高和宽高比可能不同,不同的density屏幕的宽高和宽高比可能相同)。

系统渲染布局第一步就是把dp转成px。系统在dp转换成px的时候,由于density是厂家写死的,和屏幕大小无关。这就导致在不同设备呈现的效果不一致。而我们的目的是为了让他们呈现效果一致。这就需要在dp转px的过程中人为添加和设备屏幕相关的变量。所以适配的本质就是:dp转换成px的时候根据当前设备屏幕等比例缩放效果图。

根据dp转px公式有两个思路:

从感受上来说今日头条适配方案是基于个人开发的库来适配,当使用过程中遇到问题,你不得不去阅读实现细节,否则对于你来解决问题是痛苦的。其次,个人维护项目都有随时停止维护的风险。

最小宽度适配方案是根据google提供的限定符:values-sw《X》dp。布局中使用同一个@dimens/qb引用,系统会根据:SW(屏幕最小宽度单位px)/density计算的值X ,来选择不同的values-sw《X》文件夹下的dimens文件。我们只需要保证dimens文件按照当前X值和设计图等比例生成就可以适配。并且,没有停止维护的风险。

二.原理

今日头条适配方案和最小限定符适配方案都是适配一个方向,另一个方向会和设计图出现偏差。原因是:我们无法保证市面上所有设备的高宽比例一致。选择适配一个方向,另外一个方向我们可以通过布局 包裹内容+滑动布局,撑满屏幕、权重、比例达到适配。强行宽高都适配会导致变形。

2.1今日头条适配方案原理

当前设备屏幕总宽度(单位为像素)/ 设计图总宽度(单位为 dp) = density。density不在是厂商写死的,而是根据当前设备屏幕总宽度像素动态生成的。这样按照设计图布局文件去写dp,那么AttributeSet类转换成px在不同总宽度屏幕设备上占比是相同的。那么框架代码干的事就是:动态计算不同设备的density,把系统本来的density替换成动态生成的density(仅在本应用生效)。

2.2最小限定符适配方案

一个确定的设备,匹配到的dimens文件夹也是确定的,因为根据公式:屏幕最小宽度像素不会变,density也不会变,当然屏幕最小宽度dp也不会变。

我们只需要穷举市面上:(SW(屏幕最小宽度单位px)/density计算的)值X和设计图等比例缩放即可生成不同的dimens文件。生成公式如下:

values-sw《X》dp 文件 = (X dp/设计稿的宽度dp)*[1..720]生成文件内容即可。

举个例子,小米5的dpi是480,横向像素是1080px,根据dp=px/(dpi/160),横向的dp值是1080/(480/160),也就是360dp,系统就会去寻找是否存在value-sw360dp的文件夹以及对应的dimens文件。而该文件夹下的值又是根据360dp/设计稿宽度dp等比例缩放生成的。所以可以等比例缩放。(假如没找到value-sw360dp文件夹,那么系统会向下寻找最接近value-sw360dp的文件,如value-sw359dp)

三.使用技巧

3.1今日头条方案技巧

  • 设计图的宽高(单位是dp)是多少,就在配置文件中填写多少。
  • 默认是宽度方向适配,那么对于高度方向就需要布局技巧适配。
  • java代码中设置尺寸,通过设计图获取dp,再通过dp2px方法转换后使用即可。
  • 布局文件中设置尺寸,直接设置设计图的dp即可。

3.2最小宽度适配方案注意

  • 按照哪个设计图尺寸生成的dimens,对应的查看设计图的时候就必须按照哪个尺寸去查看。

  • 适配方向是设备最小宽度方向,另外一个方向通过布局技巧去适配。

  • java代码中设置尺寸通过getResources().getDimension(R.dimen.qb)获得px值直接设置

  • 布局文件中设置尺寸,直接设置设计图的dp所对应的@dimen/qb即可。

四.场景分析

4.1场景一 app方向竖向固定,设计图一套

两个适配方案都可以使用。都是横方向自动适配,竖方向需要通过布局技巧去适配。

4.2场景二 app方向横向固定,设计图一套

两个适配方案都可以使用。今日头条适配方案还是横方向自动适配,竖方向通过布局技巧去适配。最小宽度适配方案竖方向自动适配,横方向需要通过布局技巧去适配。

4.3场景三 app方向不固定,设计图一套

今日头条方案可以自动适配方向旋转。最小宽度方案不可以自动适配方向旋转。但是google提供了另外一个限定符**values-w《X》dp,**就可以适配横竖屏切换使用一套设计图的方案。这个限定符的意思是:使用宽度适配,而非最小宽度适配。仅仅需要做的是计算出设备宽度和高度的 dp 值,全部生成 dimens.xml 文件即可。

都是横方向自动适配,竖方向需要通过布局技巧去适配。

4.4场景四 app方向不固定,设计图两套

今日头条方案可以使用无差别。最小宽度适配方案和场景一 一样去生成,res 文件夹下创建 layout-land 文件夹用来放横屏的布局文件,默认的 layout 文件夹放竖屏的布局文件。

五.思考

之前感觉app当横方向固定,我们还要去通过布局技巧去适配横方向感觉很别扭。于是乎我调整限定符为w,进而发现本质是一样的。原因是我们的app大部分都是上下滚动,所以感觉别扭。假如我们的app变成了竖方向固定,我们左右滚动布局,你也会感觉别扭。所以使用他们没有差别。但那是考虑到有状态栏和导航栏的影响,我们尽量使用限定符values-w《X》dp去做适配。

六.字体大小适配

系统在渲染布局大小第一步会把dp转换成px,布局文件中通过AttributeSet类自动转换成px,java代码中开发者自己转换成px使用(转换因子是:density )。同样的,sp也要转换成px后使用。在布局中使用sp系统会自动转换成px。(转换因子是scaledDensity)。在代码中我们都是使用setTextSize方法去设置,这个方法会调用2个参数的方法,其中一个传递unit。unit可选的值有px,dp,sp,默认sp。如果传入sp单位系统会通过转换因子scaledDensity转换成px再使用。如果传入dp单位系统会通过转换因子density转换成px在使用,如果传入px单位不用转换直接使用。

scaledDensity通过getResources().getDisplayMetrics().scaledDensity获取。

scaledDensity和density的默认值是一样的(可以通过查看源码DisplayMetrics类中setToDefaults方法看到)。

scaledDensity = density;

scaledDensity和density之间还存在一个缩放关系(可以通过查看源码ResourcesImpl类中updateConfiguration方法):

//caledDensity和density缩放关系
scaledDensity = density * (fontScale != 0 ? fontScale : 1.0f);

系统设置中调整字体大小也是通过调整fontScale的值来改变scaledDensity的值,进而改变字体大小。也就是我们想要字体大小跟随系统改变就只能使用sp值去设置。想要设置字体不跟随系统改变我们就使用dp或者px。

但是在实际开发过程中我们会遇到需求随时改变(比如一会要求字体大小跟随系统,一会要求字体大小不跟随系统)。所以字体我们最好还是按照google推荐方式使用sp去设置,以此来提高代码的健壮性。(并且当你测试使用dp做为字体大小的时候,会发现鸿蒙系统还是会出现跟随改变。因为他们让density
互相跟随scaledDensity改变)

使用sp单位,也可以动态控制app字体大小是否跟随系统。代码如下:

参考

在baseActivity中(对鸿蒙系统无效)

    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        if (newConfig.fontScale != 1) {
            getResources();
        }
    }

    @Override
    public Resources getResources() {
        Resources resources = super.getResources();
        if (resources.getConfiguration().fontScale != 1) { 0
            Configuration newConfig = new Configuration();
            newConfig.setToDefaults();
            resources.updateConfiguration(newConfig, resources.getDisplayMetrics());
        }
        return resources;
    } 

话题回到上边两个框架中如何去做字体适配:

最小宽度字体适配方案适配字体步骤:

第一步:在所有dimens文件下,复制对应的值,把单位dp改成sp(提供布局中使用文字大小)

<dimen name="qp_16">20dp</dimen>复制一份为<dimen name="sp_16">20sp</dimen>

第二步:引用方式

布局引用@dimen/sp_16设置字体大小

代码中设置字体px2sp

float pxValue = getResources().getDimension(R.dimen.sp_16);//获取对应资源文件下的sp值
int spValue = ConvertUtils.px2sp(this, pxValue);//将px值转换成sp值
mTvShowParams.setTextSize(spValue);//设置文字大小单位sp

最小宽度字体适配方案适配字体步骤:

可以通过setExcludeFontScale(true)方法设置是否跟随系统改变。true不跟随系统,false表示跟随系统改变。经过测试鸿蒙系统也是存在问题。设置跟随系统,当再次打开app,还是变成不跟随系统。