[关闭]
@TryLoveCatch 2022-04-28T17:10:18.000000Z 字数 4347 阅读 981

Android知识体系之适配

Android知识体系


适配

Android 目前最稳定和高效的UI适配方案
骚年你的屏幕适配方式该升级了!-今日头条适配方案

适配的原因

在标识尺寸的时候,当我们使用px这个真实像素单位,因为不同的手机之间,分辨率是不同的,同样像素的控件在不同分辨率的手机上显示的效果是不一样的:

由图可知,分辨率越来越高,UI显示的就越来越小。所以px这个单位在布局文件中是不推荐的。

适配的目标

这两个问题都很重要,一个是保证我们开发的高效,一个是保证我们适配的成效;

基础概念

dp

dp指的是设备独立像素,在不同分辨率和尺寸的手机上代表了不同的真实像素,比如在分辨率较低的手机中,可能1dp=1px,而在分辨率较高的手机中,可能1dp=2px。

dpi

dpi是像素密度,指的是在系统软件上指定的单位尺寸的像素数量,它往往是写在系统出厂配置文件的一个固定值。

ppi

ppi也是像素密度,但是这个是物理上的概念,它是客观存在的不会改变。

dpi和ppi区别

dp直接适配

这基本是最原始的Android适配方案。

实现

720P,和1080P的手机,dpi是不同的,一个是320dpi,一个是480dpi,这也就意味着,不同的分辨率中,1dp对应不同数量的px,720P中,1dp=2px,1080P中1dp=3px,这就实现了,当我们使用dp来定义一个控件大小的时候,他在不同的手机里表现出相应大小的像素值。

问题

同一个分辨率的dpi不一定是一致的,所以就会出现问题。
例如

  1. 360 * 480/160)= 1080px
  2. 360 * 420/160)= 945px


从上面的效果可以看到,同样是1080P的手机,差异是比较明显的。在这种情况下,我们的UI可能需要做一些微调甚至单独适配。

宽高限定符适配

宽高限定符适配,简单说,就是穷举市面上所有的Android手机的宽高像素值,来进行精确匹配。

实现

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

注意,单位是px。

如果我们的UI设计界面使用的就是基准分辨率,那么我们就可以按照设计稿上的尺寸填写相对应的dimens引用了,而当APP运行在不同分辨率的手机中时,系统会根据这些dimens引用去该分辨率的文件夹下面寻找对应的值。这样基本解决了我们的适配问题,而且极大的提升了我们UI开发的效率。

问题

需要精准命中才能适配,比如1920x1080的手机就一定要找到1920x1080的限定符,否则就只能用统一的默认的dimens文件了。而使用默认的尺寸的话,UI就很可能变形,简单说,就是容错机制很差。

smallestWidth适配

smallestWidth 限定符适配方案
smallestWidth适配,或者叫sw限定符适配。指的是Android会识别屏幕可用高度和宽度的最小尺寸的dp值(其实就是手机的宽度值),然后根据识别到的结果去资源文件中寻找对应限定符的文件夹下的资源文件。

什么是 smallestWidth

smallestWidth 翻译为中文的意思就是 最小宽度,系统会根据当前设备屏幕的 最小宽度 来匹配 values-swdp,为什么不是根据 宽度 来匹配,而要加上 最小 这两个字呢?

这就要说到,移动设备都是允许屏幕可以旋转的,当屏幕旋转时,屏幕的高宽就会互换,而这个方案是不区分屏幕方向的,它只会把屏幕的高度和宽度中值最小的一方认为是 最小宽度,这个 最小宽度 是根据屏幕来定的,是固定不变的,意思是不管您怎么旋转屏幕,只要这个屏幕的高度大于宽度,那系统就只会认定宽度的值为 最小宽度,反之如果屏幕的宽度大于高度,那系统就会认定屏幕的高度的值为 最小宽度。

如果想让屏幕宽度随着屏幕的旋转而做出改变该怎么办呢?可以再根据 values-wdp (去掉 sw 中的 s) 生成一套资源文件。

如果想区分屏幕的方向来做适配该怎么办呢?那就只有再根据 屏幕方向限定符 生成一套资源文件咯,后缀加上 -land 或 -port 即可,像这样,values-sw400dp-land (最小宽度 400 dp 横向),values-sw400dp-port (最小宽度 400 dp 纵向)

实现

这种机制和上文提到的宽高限定符适配原理上是一样的,都是系统通过特定的规则来选择对应的文件。
smallestWidth限定符适配和宽高限定符适配最大的区别在于,前者有很好的容错机制,如果没有value-sw360dp文件夹,系统会向下寻找,比如离360dp最近的只有value-sw350dp,那么Android就会选择value-sw350dp文件夹下面的资源文件。这个特性就完美的解决了上文提到的宽高限定符的容错问题。

问题

今日头条适配方案

今日头条适配方案

实现

核心原理:

  1. px = dp * density
  2. density= DPI / 160

不管你在布局文件中填写的是什么单位,最后都会被转化为 px,系统就是通过下面的方法,将你在项目中任何地方填写的单位都转换为 px 的

  1. public static float applyDimension(int unit, float value,
  2. DisplayMetrics metrics){
  3. switch (unit) {
  4. case COMPLEX_UNIT_PX:
  5. return value;
  6. case COMPLEX_UNIT_DIP:
  7. return value * metrics.density;
  8. case COMPLEX_UNIT_SP:
  9. return value * metrics.scaledDensity;
  10. case COMPLEX_UNIT_PT:
  11. return value * metrics.xdpi * (1.0f/72);
  12. case COMPLEX_UNIT_IN:
  13. return value * metrics.xdpi;
  14. case COMPLEX_UNIT_MM:
  15. return value * metrics.xdpi * (1.0f/25.4f);
  16. }
  17. return 0;
  18. }

density 在公式的运算中扮演着至关重要的一步

例子

可以看到屏幕的总 dp 宽度在不同的设备上是会变化的,但是我们在布局中填写的 dp 值却是固定不变的。这会导致什么问题呢?

假设我们布局中有一个 View 的宽度为 100dp

可以看到这个 View 在像素越高的屏幕上,dp 值虽然没变,但是与屏幕的实际比例却发生了较大的变化,所以肉眼的观看效果,会越来越小,这就导致了传统的填写 dp 的屏幕适配方式产生了较大的误差。

这时我们要想完美适配,那就必须保证这个 View 在任何分辨率的屏幕上,与屏幕的比例都是相同的。
那么,现在的问题就是:

100/360和100/411,我们要确保结果是一致的

那么,很明显,有两种方式:

第一种就是smallestWidth适配方案
第二种就是马上要说的方案。

那么 就需要修改屏幕的宽度dp,而上面我们说过

  1. px = dp * density

那么

宽度dp值 = 宽度的px值/density

每一个设备的px值都是不一样的,那么我们就可以通过修改density来,保证宽度dp值是我们想要的那个。

我们假设设计图宽度为360dp,那么还是上面的例子,我们修改设备2的density

  1. 1440px / 360dp = 4

这样,我们设置设备2的density为4,就可以保证设备2的宽度为360dp
如何设置呢?

  1. private void setCustomDensity(@NonNull Activity activity, @NonNull Application application) {
  2. //application
  3. final DisplayMetrics appDisplayMetrics = application.getResources().getDisplayMetrics();
  4. //计算宽为360dp 同理可以设置高为640dp的根据实际情况
  5. final float targetDensity = appDisplayMetrics.widthPixels / 360;
  6. final int targetDensityDpi = (int) (targetDensity * 160);
  7. appDisplayMetrics.density = appDisplayMetrics.scaledDensity = targetDensity;
  8. appDisplayMetrics.densityDpi = targetDensityDpi;
  9. //activity
  10. final DisplayMetrics activityDisplayMetrics = activity.getResources().getDisplayMetrics();
  11. activityDisplayMetrics.density = appDisplayMetrics.scaledDensity = targetDensity;
  12. activityDisplayMetrics.densityDpi = targetDensityDpi;
  13. }

简而言之,是通过修改density值,强行把所有不同尺寸分辨率的手机的宽度dp值改成一个统一的值,这样就解决了所有的适配问题。
比如,设计稿宽度是360dp,那么开发这边就会把目标dp值设为360dp,在不同的设备中,动态修改density值,从而保证(手机像素宽度)px/density这个值始终是360dp,这样的话,就能保证UI在不同的设备上表现一致了。

问题

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注