Android UI适配方案

「这是我参与2022首次更文挑战的第2天,活动详情查看:2022首次更文挑战」。

大纲

  1. 使用dp而不是px
  2. 尽量使用自动适配布局,而不要指定分辨率
  3. 使用宽高限定符
    1. values-1080x1920,以1080P为基准计算每种常见分辨率对应的尺寸。
    2. 需要尽可能全的添加各种设备的分辨率(有工具)
    3. 容错性不足,如果设备分辨率不能精确匹配对应限定符,会默认使用统一默认的dimens
  4. 第三方自动适配UI框架
    1. 原理:自定义RelativeLayout,在onMeasure中对控件分辨率做变换
    2. 第三方框架,维护性很成问题
    3. 一些自定义View,处理比较麻烦
  5. 最小宽度限定符,类似宽高限定符
    1. values-sw240dp,同样以某一dp宽度为基准计算其他宽度dp的值
    2. values-sw360dp、values-sw480dp
    3. 相比宽高限定符,最小宽度限定符不进行精确匹配,会遵循就近原则,可以较好的解决容错问题。
    4. 如:设备宽364dp,系统会自动就近配置values-sw360dp下的dimens,显示效果相差不会很大
  6. 今日头条——修改density值
    1. 原理:px = dp x (dpi/160) = dp x density
    2. 既然如此,将density
    3. 需要UI出设计图时以统一的dp为基准
    4. mp.weixin.qq.com/s/d9QCoBP6k…

基本概念

  • 像素——px
  • 密度独立像素——dp或dip
  • 像素密度——dpi,单位面积内的像素数。
    • 软件系统的概念。
    • 在系统出厂时,配置文件中的固定值。
    • 通常的取值有:160、240、360、480等。
    • 不同于物理概念上的屏幕密度ppi,如ppi为415、430和470时,dpi可能会统一设置为480。
  • density——当dpi=160时,1px = 1pd,此时denstiy的值为1,dpi=240时,1.5px = 1dp,density的值为1.5。
  • 上述值的关系:
    • denstiy = dpi / 160;
    • px = dp x density = dp x (dpi / 160)

Android设备的碎片化极为严重,各种尺寸和分辨率的设备无比繁多。使得在Android开发中,UI适配变成了开发过程中极为重要的一步。为此Google提出了密度独立像素dip或dp的概率,旨在更友好的处理Android UI适配问题。

但是效果嘛,只能说差强人意,可以解决大部分的业务场景,但是剩下的个别情况就搞死人了,原因在于Android设备碎片化实在太严重了,存在各种分辨率和dpi的设备。

比如两台设备A和B,分辨率是1920x1080,dpi分别为420和480,在布局中编写一个100dp宽的ImageView,按照上面的公式ImageView的显示宽度分别为:100dp x 420 / 160 = 262.5100dp x 480 / 160 = 300,ImageView在B设备上明显显示要大一些。差异可能还不明显,我们把宽度改为360dp呢,A设备显示宽度为:948px,B设备显示宽度为:1080px。这就扯淡了,一个宽度填充满屏幕,一个不满。这种情况肯定是需要开发来背锅解决的。

适配方案

虽然上面提到了使用dp无法解决全部业务场景,但是相对于直接使用px已经可以解决大部分场景下的适配问题了。

所以UI适配的第一条就是:

1. 使用dp代替px来编写布局。

又因为上面无法适配的个别场景,所以UI适配的第二条是:

2.尽量使用自动适配布局,而不要指定分辨率

这一条也很好理解,尽量使用ConstraintLayout 约束布局和LinearLayout等父布局,不要写死分辨率,比如上面的例子如果使用match_parent而不是360dp,也可以避免出现显示不一致问题(但是仅限于上列)。

限定符

Google同样意识到dp满足所以业务场景的需要,所以提供了宽度限定符的概念。

虽然您的布局应始终通过拉伸其视图内部和周围的空间来应对不同的屏幕尺寸,但这可能无法针对每种屏幕尺寸提供最佳用户体验。例如,您为手机设计的界面或许无法在平板电脑上提供良好的体验。因此,您的应用还应提供备用布局资源,以针对特定屏幕尺寸优化界面设计。

最小宽度限定符

使用“最小宽度”屏幕尺寸限定符,您可以为具有最小宽度(以密度无关像素 dp 或 dip 为度量单位)的屏幕提供备用布局。

通过将屏幕尺寸描述为密度无关像素的度量值,Android 允许您创建专为非常具体的屏幕尺寸而设计的布局,同时让您不必对不同的像素密度有任何担心。

通俗一点翻译就是:可用通过xxxx-swXXXdp的方式定义一些最小限定符的资源文件,比如:values-sw400dp、values-sw600dp,系统会自动匹配如屏幕宽度相近资源文件夹。

我们再来看上面的例子两台设备A和B,分辨率是1920x1080,dpi分别为360和400。我们简化下问题比如设计图给的是1920x1080 360dpi,包含一个22.5px * 22.5px = 10dp * 10dp的图片。按经验布局应该如下编写:

<ImageView
    android:id="@+id/img_iv"
    android:layout_width="10dp"
    android:layout_height="10dp"
    android:background="@mipmap/ic_launcher"/>
复制代码

在不同设备上运行的结果:

  • 1280 x 720 240dpi的设备,图片显示为15px * 15px;
  • 1920 x1080 360dpi的A设备,图片显示为22.5px * 22.5px;
  • 1920 x1080 400dpi的B设备,图片显示为25px * 25px;

可以看到B设备图片显示是有问题的,为了解决这个问题,我们使用最小宽度限定符定义两个资源文件夹:values-sw360dp和values-sw400dp。

在values-sw360dp中添加dimen.xml内容如下:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <dimen name="dp_1">1dp</dimen>
    <dimen name="dp_2">2dp</dimen>
    <dimen name="dp_3">3dp</dimen>
    <dimen name="dp_4">4dp</dimen>
    <dimen name="dp_5">5dp</dimen>
    <dimen name="dp_6">6dp</dimen>
    <dimen name="dp_7">7dp</dimen>
  <!-- 省略其他值 -->
  	<dimen name="dp_360">360dp</dimen>
    <!-- 因为设计图是360dpi,所以控件尺寸通常不会超过360dp,定义最大360dp的值足够使用 -->
</resources>
复制代码

在values-sw420dp中添加dimen.xml,文件中的dimen值很容易换算出来:在360dpi中dp_1 = 1dp,那么在400dpi中dp_1 = 360 / 400 = 0.9dp,文件内容如下:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <dimen name="dp_1">0.9dp</dimen>
    <dimen name="dp_2">1.8dp</dimen>
    <dimen name="dp_3">2.7dp</dimen>
    <dimen name="dp_4">3.6dp</dimen>
    <dimen name="dp_5">4.5dp</dimen>
    <dimen name="dp_6">5.4dp</dimen>
    <dimen name="dp_7">6.3dp</dimen>
  	<!-- 省略其他值 -->
</resources>
复制代码

注意要在values文件夹下添加默认dimen.xml,文件内容与values-sw360dp中添加dimen.xml一致(因为设计图恰好是360dpi的)。

布局中的ImageView自然要改写为:

<ImageView
    android:id="@+id/img_iv"
    android:layout_width="@dimen/dp_10"
    android:layout_height="@dimen/dp_10"
    android:background="@mipmap/ic_launcher"/>
复制代码

我们再来看一下不同设备运行结果:

  • 1280 x 720 240dpi的设备,未匹配到限定符使用values中的dimen,dp_10 = 10dp, px = 10 * 240 / 160 = 15px,图片显示尺寸为15px * 15px。
  • 1920 x1080 360dpi的A设备,匹配到sw360dp限定符,dp_10 = 10dp, px = 10 * 360 / 160 = 20px,图片显示尺寸为22.5px * 22.5px。
  • 1920 x1080 400dpi的B设备,匹配到sw420dp限定符,dp_10 = 9dp, px = 9 * 400 / 160 = 20px,图片显示尺寸为22.5px * 22.5px。

完美的解决了设备A和B的显示问题,所以UI适配的第三条是:

3. 使用最小(可用)宽度限定符,解决同样分辨率不同dpi的设备适配问题。

这种方案看似完美,但是也有一些隐含的问题:此方案只能解决同样分辨率不同dpi设备的适配问题:

  • 一旦出现不同分辨率相同dpi的情况就无效了(当然这种情况的可能性不高)。
  • 以上举例只是基于1920x1080这一种分辨率为例说明,试想一下如果1280x720的设备存在240dpi和280dpi的情况呢?我们只能针对特殊情况适配处理,无法解决全部场景适配问题。

宽高限定符

类似于上面说的最小宽度限定符,但是需要精确指定要匹配的设备宽高,values-1920x1080、values-1280x720等。配置与使用方式也与上面类似,如设计图尺寸为1920x1080 360dpi,那么只需要以1920x1080为基准计算所有分辨率对应的尺寸就可以了,布局编写时按照给的尺寸一一对应就可以,比如:给出的ImageView是20px*20px的,那在布局中同样指定width和height为@dimen/dp_20就可以了。

values-1920x1080中dimens.xml如下:

<resources>
    <dimen name="dp_1">1px</dimen>
    <dimen name="dp_2">2px</dimen>
    <dimen name="dp_3">3px</dimen>
    <dimen name="dp_4">4px</dimen>
    <dimen name="dp_5">5px</dimen>
    <dimen name="dp_6">6px</dimen>
    <dimen name="dp_7">7px</dimen>
    <dimen name="dp_8">8px</dimen>
    <dimen name="dp_9">9px</dimen>
  	<!-- 省略其他 -->
  	<dimen name="dp_1920">1920px</dimen>
</resources>
复制代码

values-1280x720中dimens.xml换算为:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <dimen name="dp_1">0.66px</dimen>
    <dimen name="dp_2">1.33px</dimen>
    <dimen name="dp_3">2.0px</dimen>
    <dimen name="dp_4">2.66px</dimen>
    <dimen name="dp_5">3.33px</dimen>
    <dimen name="dp_6">4.0px</dimen>
    <dimen name="dp_7">4.66px</dimen>
  	<!-- 省略其他 -->
</resources>
复制代码

同样需要在values添加默认尺寸dimen.xml,内容同基准分辨率文件。

因为不是所有设备屏幕都是16:9的,也可以按照宽高拆分成两个dimens.xml文件dimen_x.xml和dimen_y.xml,按照宽高:1920x1080分别换算得到x和y的值,但是页面设计通常是竖屏可滑动的,所以对高度不敏感,只需要根据一个维度计算统一值就可以了。

以上计算方式比较简单了,不需要自己编写换算可以通过代码工具或者自己写个类实现。(网上有好多,找一下应该可以找到)。

理论上只要尽可能多的枚举所有设备分辨率,就可以完美的解决屏幕适配问题,所以UI适配的第四条是:

4.使用宽高限定符,精确匹配屏幕分辨率。

这种方案已经近乎完美了,一度成为比较热门的解决方案,也有很多团队使用过此方案。但是之前也说过Android设备的碎片化太严重了,综合考虑基本不可能在项目中枚举所有的屏幕尺寸进行适配,如果设备没有匹配到对应尺寸会使用values下的默认尺寸文件,可能会出现严重的UI适配问题。

但是不可否认此种方案实现简单,对于编写布局也很友好(直接填入设计图的尺寸值就行,不需要换算),可以解决绝大多数的设备适配问题,是一种很友好的解决方案。

第三方UI适配框架

有很多第三方库的解决方案,是从ViewGroup入手的,要么重写常用的如:RelativeLayout、LinearLayout和FrameLayout等在控件内部做转换来适配不同尺寸的设备,要么提供新的Layout如:Google的PercentLayout布局。但是这些方案基本都不在维护了,这里就不详细展开了,感兴趣的可以自行搜索了解。

UI适配的第五条是:

5. 使用第三方自适配框架,解决UI适配问题。

感兴趣的可以参考以下文档:

其他适配方案

参考字节的实现方案:

一种极低成本的Android屏幕适配方式

这篇文章着实属于拾人牙慧了,起因是因为看到了这篇博客Android 目前最稳定和高效的UI适配方案。所以想着确实应该把这部分知识梳理一下,所以写了这篇文档加了一些自己的里面,主要也是为了梳理知识点加深理解。

文中列举的几种UI适配方案,没有严格的优劣之分,可以根据自己的业务需求选择,也可以选择几种搭配使用,比如笔者目前主要做智能电视(盒子)的应用开发,Android电视不同于手机,碎片化没有那么严重,电视分辨率种类屈指可数,所以在日常项目中基本选择使用宽高限定符的方案进行适配,效果也是极好的。

猜你喜欢

转载自juejin.im/post/7065185866674601998