Android 屏幕适配知识点

屏幕适配

方案原理实现
今日头条适配方案在渲染前会将 dp 转为 px
px = density * dp;

效果:以 宽 或 高 一个维度去适配,保持该维度上与设计图一致

布局文件中 dp 的转换:调用 TypedValue # applyDimension() 进行转换
图片的 decode,BitmapFactory # decodeResourceStream()
最终都是通过 DisplayMetrics # density 实现转换

实现:
修改 DisplayMetrics # density 的值,使不同设备 dp 转 px相同
修改 DisplayMetrics # scaledDensity 的值,使不同设备 sp 转 px相同
修改 DisplayMetrics # densityDpi 的值

假设 设计图宽度参照标准为 360dp
计算公式: density = 屏幕真实宽(单位px) / 360
(用设计图参考的设备密度,代替真实的设备密度)
1、在 Activity # onCreate 方法中,重新设置 DisplayMetrics # density、DisplayMetrics # scaledDensity、DisplayMetrics # densityDpi

假设设计图参照标准为 360dp,
则 新 targetDensity = DisplayMetrics # widthPixels / 360

将 targetDensity 赋值给 DisplayMetrics # density

2、在 Activity # onCreate 方法中,application.registerComponentCallbacks() 监听 onConfigurationChanged() 回调函数

3、onConfigurationChanged() 中,如果字体改变,则获取新的字体大小 targetScaledDensity = targetDensity * (DisplayMetrics # scaledDensity / DisplayMetrics # density)
(DisplayMetrics # density 要取修改前的值)

原理:newScaledDensity / newDensity = oldScaledDensity / oldDensity


将 targetScaledDensity 赋值给 DisplayMetrics # scaledDensity

4、用新的 density 计算新的 设备密度
targetDensityDpi = (int) (160 * targetDensity)

将 targetDensityDpi 赋值给 DisplayMetrics # densityDpi
宽高限定符 方案values
values-480x320
values-800x480
values-854x480

原理:系统从与 设备分辨率匹配 的文件夹下寻找对应值

缺陷:容错机制差,需要精准命中才能适配,否则会使用默认的 dimens 文件
1、设定一个基准分辨率,其他分辨率 根据这个基准分辨率来计算
2、不同分辨率文件夹,根据基准分辨率按比例计算,编写对应的 dimens 文件

比如,以 480x320 为基准分辨率:
宽度为 320,将任何分辨率的宽度整分为 320份,取值为 x1-x320
高度为 480,将任何分辨率的高度整分为 480份,取值为 y1-y480


在 values-480x320/dimens.xml中:
<dimen name=“x1”>1px</dimen>
<dimen name=“x2”>2px</dimen>
<dimen name=“x3”>3px</dimen>

那么,对于 800x480 的分辨率的 dimens 文件来说:
480 / 320 = 1.5,480 分成320份,每份就是 1.5
所以 800x480目中的对应值,要设置为 480x320 目录的1.5倍

在 values-800x480/dimens.xml中:
<dimen name=“x1”>1.5px</dimen>
<dimen name=“x2”>3px</dimen>
<dimen name=“x3”>4.5px</dimen>

其他目录用相同的方法计算后填写

当APP 运行在不同分辨率的手机中时,系统会根据这些dimens引用,
去与设备分辨率匹配的文件夹下寻找对应值
smallestWidth 方案values
values-sw320dp
values-sw360dp
values-sw384dp

系统识别屏幕可用高度、宽度的最小尺寸的 dp 值
不区分屏幕方向
它只会把屏幕的高度、宽度中,值最小的一方认为是 最小宽度
最小宽度 是根据屏幕来定的,是固定不变的

比如,
设备 dpi 为480
横向像素为 1080px,

根据
density = dpi / 160 = 480/160 = 3;
px = dp * density;

横向的 dp 值是 1080 / density = 1080 / 3 = 360dp;
系统会去寻找 values-sw360dp 的文件夹,及对应的资源文件
比如设计稿以宽度为 320dp为基准,
那么,values-sw320dp 中 dimens.xml 内容为:
<dimen name=“x1”>1dp</dimen>
<dimen name=“x2”>2dp</dimen>
<dimen name=“x3”>3dp</dimen>

<dimen name=“x320”>320dp</dimen>

480 / 320 = 1.5

那么,values-sw480dp 中 dimens.xml 对应值为:
<dimen name=“x1”>1.5dp</dimen>
<dimen name=“x2”>3dp</dimen>
<dimen name=“x3”>4.5dp</dimen>

<dimen name=“x320”>480dp</dimen>

smallestWidth适配,有很好的容错机制
如果没有 values-sw360dp 文件夹,系统会往下寻找
解决了宽高限定符的容错问题


比如,离360dp最近的只有 values-sw350dp,
那么 系统会选择 values-sw350dp 文件夹下面的资源文件

一、今日头条适配方式

1、问题

dp 在渲染前会将 dp 转为 px计算公式

density = dpi / 160;		
px = density * dp;
px = dp * (dpi / 160);

dpi 设备密度:物理屏幕每英寸的像素数。不同设备可能不同。

比如,同样分辨率 1080*1920,屏幕尺寸不同时,dpi也不同。
设计师出图一般是参照某个分辨率和屏幕尺寸来设计的,
那么在【相同分辨率不同屏幕尺寸的手机上,显示的效果就不一致】了。

(不同分辨率的适配,android系统本身通过不同资源文件夹的方式来处理了)

2、实现目标

1)以 宽 或 高 一个维度去适配,保持该维度上与设计图一致

2)支持 dp 和 sp 单位,控制迁移成本到最小。

3、解决方案

dp 和 px 转换公式:px = dp * density
修改 DisplayMetrics # density 的值,使不同设备 dp 计算得到的 px 值相同
修改 DisplayMetrics # scaledDensity 的值,使不同设备 sp 计算得到的 px 值相同

public class DisplayMetrics {
	//屏幕密度与资源目录密度比
	public float density;
	
	//屏幕密度 dpi
	public int densityDpi;
	
	//字体缩放因子,正常情况下与 density 相等
	//但是调节系统字体大小后会改变这个值。
	public float scaledDensity;
}

density 是 DisplayMetrics 中的成员变量,
而 DisplayMetrics 实例通过 Resources # getDisplayMetrics 可以获得,
而 Resouces 通过 Activity、Application的Context 获得。

布局文件中 dp 的转换
调用 TypedValue # applyDimension(int unit, float value, DisplayMetrics metrics) 进行转换

图片的 decode,BitmapFactory # decodeResourceStream()

所有 dp 和 px 的转换,都是通过 DisplayMetrics 中的值来计算的。
因此,只需要修改 DisplayMetrics 中和 dp 转换相关的变量即可。

UI 设计图宽度参照标准为 360dp
新的密度计算公式 density = 屏幕真实宽(单位px)/ 360
用设计图参考的设备密度,代替真实的设备密度

4、代码实现

下面假设设计图宽度是360dp,以宽维度来适配

1、在 Activity # onCreate 方法中,重新设置 DisplayMetrics # density、DisplayMetrics # scaledDensity
1)假设设计图参照标准为 360dp,则 新 targetDensity = DisplayMetrics # widthPixels / 360
2)将 targetDensity 赋值给 DisplayMetrics # density

2、在 Activity # onCreate 方法中,application.registerComponentCallbacks() 监听 onConfigurationChanged() 回调函数
3、onConfigurationChanged() 中,如果字体改变,则获取新的字体大小
targetScaledDensity = targetDensity * (DisplayMetrics # scaledDensity / DisplayMetrics # density)
(DisplayMetrics # density 要取修改前的值)
原理:newScaledDensity / newDensity = oldScaledDensity / oldDensity

将 targetScaledDensity 赋值给 DisplayMetrics # scaledDensity

4、 用新的 density 计算新的 设备密度
targetDensityDpi = (int) (160 * targetDensity)

将 targetDensityDpi 赋值给 DisplayMetrics # densityDpi

private static float sNoncompatDensity;
private static float sNoncompatScaledDensity;

public static void setCustomDensity(Activity activity, Application application) {
    //获取application的DisplayMetrics
    final DisplayMetrics appDisplayMetrics = application.getResources().getDisplayMetrics();
    if (sNoncompatDensity == 0) {
        sNoncompatDensity = appDisplayMetrics.density;
        sNoncompatScaledDensity = appDisplayMetrics.scaledDensity;
        application.registerComponentCallbacks(new ComponentCallbacks() {
            @Override
            public void onConfigurationChanged(Configuration newConfig) {
                if (newConfig != null && newConfig.fontScale > 0) {//字体改变后,将appScaledDensity重新赋值
                    sNoncompatScaledDensity = application.getResources().getDisplayMetrics().scaledDensity;
                }
            }
            @Override
            public void onLowMemory() {
            }
        });
    }
    //UI设计图宽度参照标准为 360dp
    //新的密度计算公式  density = 屏幕真实宽(单位px)/360
    final float targetDensity = appDisplayMetrics.widthPixels / 360;
    final float targetScaledDensity = targetDensity * (sNoncompatScaledDensity / sNoncompatDensity);
    final int targetDensityDpi = (int) (160 * targetDensity);

    appDisplayMetrics.density = targetDensity;
    appDisplayMetrics.scaledDensity = targetScaledDensity;
    appDisplayMetrics.densityDpi = targetDensityDpi;

    final DisplayMetrics activityDisplayMetrics = activity.getResources().getDisplayMetrics();
    activityDisplayMetrics.density = targetDensity;
    activityDisplayMetrics.scaledDensity = targetScaledDensity;
    activityDisplayMetrics.densityDpi = targetDensityDpi;
}

二、宽高限定符适配方式

1、列举市面上所有Android 手机的宽高像素值。
values
values-480x320
values-800x480
values-854x480
values-960x540
values-1024x600
values-1024x768
values-1184x720
values-1196x720
values-1280x720
values-1280x800
values-1812x1080
values-1920x1080
values-2560x1440
2、设定一个基准分辨率

其他分辨率都根据这个基准分辨率来计算,
在不同的尺寸文件夹内部,根据该尺寸编写对应的 dimens 文件。

比如,以 480x320 为基准分辨率:

宽度为 320,将任何分辨率的宽度整分为 320份。
高度为 480,将任何分辨率的高度整分为 480份。

在 values-480x320/dimens.xml中

<dimen name="x1">1px</dimen>
<dimen name="x2">2px</dimen>
<dimen name="x3">3px</dimen>

那么,对于 800x480 的分辨率的 dimens 文件来说:

480/320 = 1.5,480 分成320份,每份就是 1.5
所以 800x480目中的对应值,要设置为 480x320 目录的1.5倍

在 values-800x480/dimens.xml中

<dimen name="x1">1.5px</dimen>
<dimen name="x2">3px</dimen>
<dimen name="x3">4.5px</dimen>

其他目录用相同的方法计算后填写。

这时候,如果UI设计界面使用的是我们的基准分辨率,
那么我们就可以按设计稿上的尺寸填写相应的 dimes 引用了。
当APP 运行在不同分辨率的手机中时,系统会根据这些dimens引用,
去与设备分辨率匹配的文件夹下寻找对应值。

3、缺陷

容错机制差,需要精准命中才能适配

比如,1920x1080 的手机,一定要找到 1920x1080 的限定符,否则会使用默认的 dimens 文件

三、smallestWidth适配(sw限定符适配)

1、关于smallestWidth 适配

系统识别屏幕可用高度、宽度的最小尺寸的dp值
根据识别到的结果去资源文件中,寻找对应限定符的文件夹下的资源文件

为什么不是根据 宽度 来匹配,而要加上 最小 这两个字呢?
移动设备都是允许屏幕可以旋转的,当屏幕旋转时,屏幕的高宽就会互换,
加上 最小 这两个字,是因为 这个方案是不区分屏幕方向的,
它只会把屏幕的高度、宽度中,值最小的一方认为是 最小宽度

最小宽度 是根据屏幕来定的,是固定不变的。
不管怎么旋转屏幕,
只要这个屏幕的高度大于宽度,那系统就只会认定宽度的值为 最小宽度,
反之如果屏幕的宽度大于高度,那系统就会认定屏幕的高度的值为 最小宽度。

values
values-sw320dp
values-sw360dp
values-sw384dp
values-sw400dp
values-sw432dp
values-sw480dp
values-sw533dp
values-sw600dp

比如,
设备 dpi 为480,横向像素为 1080px,

根据
density = dpi / 160 = 480/160 = 3;
px = dp * density;

横向的 dp 值是 1080 / density = 1080 / 3 = 360dp;
系统会去寻找 values-sw360dp 的文件夹,及对应的资源文件。

2、dimens 文件编写

比如设计稿以宽度为 320dp为基准,
那么,values-sw320dp 中 dimens.xml 内容为:

<dimen name="x1">1dp</dimen>
<dimen name="x2">2dp</dimen>
<dimen name="x3">3dp</dimen>
...
<dimen name="x320">320dp</dimen>

那么,values-sw480dp 中 dimens.xml 对应值为:

480 / 320 = 1.5

<dimen name="x1">1.5dp</dimen>
<dimen name="x2">3dp</dimen>
<dimen name="x3">4.5dp</dimen>
...
<dimen name="x320">480dp</dimen>	
3、smallestWidth适配 与 宽高限定符适配的区别

smallestWidth适配,有很好的容错机制,
如果没有 values-sw360dp 文件夹,系统会往下寻找。解决了宽高限定符的容错问题
比如,离360dp最近的只有 values-sw350dp,那么 系统会选择 values-sw350dp 文件夹下面的资源文件

4、smallestWidth适配文件生成工具

点击进入上文的github项目,下载到本地,然后运行该Java工程,会在本地根目录下生成相应的文件,
如果需要生成更多尺寸,在DimenTypes 文件中填写你需要的尺寸即可。

转载:
拉丁吴老师的–Android 目前稳定高效的UI适配方案
今日头条适配方式
SmallestWidth 限定符适配方案

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值