Android 碎片化说法由来已久,多年以来,有那么一群苦逼的 Android 开发,他们饱受 Android 碎片化之苦,面对着各式各样的手机屏幕尺寸和分辨率,还要与"凶残"的产品和 UI 设计师过招,最近这两年这个趋势似乎还愈演愈烈,刘海屏、全面屏,还有即将推出的柔性折叠屏,UI 适配将变得越来越复杂(其实 Android 碎片化并不局限于手机屏幕,它还包括品牌、机型、设备尺寸、分辨率等众多)。

然而 Android 屏幕碎片化并没有像大家想象的那样头疼《对于开发者来说,屏幕碎片化并不算个事儿》。Google 早已经给了我们界面视图布局工具,你可以自定义一种或多种界面视图,以适应不同尺寸的设备。

关于屏幕适配我们需要重点搞明白两个问题,如下图:

屏幕适配

屏幕相关的概念以及解决屏幕适配的相关方案有哪些?本文也主要是围绕这两个核心点展开叙述。

1、屏幕与适配

作为消费者来说,通常会比较关注屏幕的尺寸、分辨率以及厚度这些指标。Android 碎片化并不局限于屏幕,但是屏幕的差异却是碎片化问题的“中心”。屏幕从 3 英寸到 10 英寸,分辨率从 320 到 1920 应有尽有,对我们 UI 适配造成很大困难。

除此之外,材质也是屏幕至关重要的评判因素。目前手机主流的屏幕可分为两大类:一类是 LCD(Liquid Crystal Display),既液晶显示器;另一种是 OLED(Origanic Light - Emitting Diode)既有机发光二极管。

最新的旗舰机例如华为 Mate 20 Pro 和 iPhone XS Max 使用的都是 OLED 屏幕。OLED 屏幕相比 LCD 在屏幕色彩、可弯曲程度、厚度以及耗电都有优势。正因为这些优势,全面屏、曲面屏、以及未来的柔性折叠屏,使用的都是 OLED 材质。

关于 OLED 与 LCD 的具体差别,可以参考《OLED 和 LCD 区别》和《手机屏幕的前世今生,可能比你想象的还精彩》,不过目前 LCD 屏幕相比 OLED 成本更具有优势。

对于碎片化的问题,Android 推荐使用 db 作为尺寸单位来适配 UI,因此每个 Android 开发都应该熟悉 px、dp、dpi、ppi、density 这些概念。

屏幕尺寸单位

通过 dp 加上自适应布局可以基本解决屏幕碎片化的问题,也是 Android 推荐使用的屏幕兼容性适配方案。但是它会存在两个比较大的问题:

1. 不一致性。因为 dpi 与实际 ppi 的差异性,导致在相同分辨率的手机上,控件的实际大小会有所不同。

2. 效率。设计师的设计稿都是以 px 为单位的,开发人员为了 UI 适配,需要手动通过百分比估算出 dp 值。

除了直接 dp 适配之外,目前业界比较常用的 UI 适配方法主要有下面几种:

1. 限制符适配方案。主要有宽高限定符与 smallestWidth 限定符适配方案。

2. 今日头条适配方案。通过反射修改系统的 density 值,具体可以参考《一种极低成本的 Android 屏幕适配方式》《今日头条适配方案》

另外,适当舍弃也是对抗 Android 碎片化的重要武器!

2、屏幕适配概论

(1)屏幕尺寸

屏幕尺寸是指屏幕对角线的长度,单位:英寸。1 英寸 = 2.54 cm。目前最主流的 Android 手机屏幕可能都已经是 6 英寸以上。

屏幕尺寸

(2)屏幕分辨率

分辨率是指屏幕横向和纵向像素点数,单位:px,每 px 代表屏幕一个物理像素点。px = density * dp。

屏幕分辨率

(3)像素密度(dpi)

dpi 指的是在系统软件上指定的单位尺寸的像素数量,跟 ppi 不同的是,dpi 可能会被人为的调整。dpi 约等于 ppi。

          像素密度(ppi)

ppi 指的是每英寸所包含的像素数目,这个是屏幕物理参数。ppi 约等于 dpi。

屏幕像素密度

像素密度是清晰度很重要的一个概念,既像素密度越高代表显示屏能够以更高的密度显示图像。当然,显示密度越高,拟真度就越高。画面细节就越丰富。

(4)dp,与密度无关的尺寸和位置

dp(density-independent pixels) 是基于屏幕物理分辨率一个抽象的单位,用于说明与密度无关的尺寸和位置。

dp 与 px 的关系:Android 规定以 160 dpi 为基准,既像素密度为 160dpi 时,此时 1dp = 1px。如果像素密度是 320dpi,此时 1dp = 2 px。

公式:px = dp * (dpi / 160)

前面我们也有提到 dp 加自适应布局基本可以解决屏幕碎片化问题,这也是 Android 官方推荐适配方案,那 dp 是如何实现适配的呢?其实毫不夸张的说:在 Android 中要彻底理解 dp 与 px 之间的关系,才是理解 Android 系统对屏幕适配的原理

下面我们通过一个小例子进行说明 dp 的适配原理。我们分别有两台手机设备,分辨率分别为:480 * 320 和 800 * 480。现在我们使用 px 作为尺寸单位,一个 240px * 160px 的控件(红框部分),如下图(画的有些不太标准):

db 自适应布局

此时该控件在 480 * 320 分辨率的手机屏幕时,控件宽度占屏幕的二分之一,当在 800 * 480 分辨率时,此时控件宽度占屏幕的三分之一了。

我们将单位 px 修改为 dp,既控件为 240dp * 160dp,效果如下图:

db 自适应布局

由于 480 * 320 分辨率的像素密度为 160dpi,而 800 * 480 分辨率的像素密度为 240dpi。

根据上面公式 px = dp * (dpi / 160):

480 * 320 分辨率屏幕 :px = 160 * (160 / 160) = 160px

800 * 480 分辨率屏幕 :px = 160 * (240 / 160) = 240px

此时不论是在 480 * 320 分辨率屏幕还是 800 * 480 分辨率屏幕,该控件的宽度始终占屏幕宽度的一半。

(5)density,密度

密度,屏幕上每平方英寸中含有的像素点数量。density = dpi / 160。

密度目录

(6)sp,Scaled Pixels

(scaled pixels)在 Android 中主要用于设置文字大小,可以根据文字大小首选项进行缩放。效果类似 dp。使用时 Google 推荐以偶数为单位,奇数会影响运算和精度问题。

在 Android 中应该始终保持使用 sp 为文字大小的单位,dp 作为其它元素的尺寸单位。另外还可以考虑矢量图,而不是位图

资源像素密度文件

先来看张图,资源目录对应像素密度范围,相信每个 Android 开发人员都不会对其感到陌生:

密度目录对应 dp 范围

不同名称的 dpi 文件对应不同的 像素密度范围,其实这也是 Android 系统默认屏幕适配的原理,它会根据目标手机的像素密度到对应资源目录下去查找相关资源。

对照 dp 与 px 之间的关系,根据 px = dp * (dpi / 160)

dp 与 px

但是实际开发过程中,我们还要考虑最终打包的大小,一般不会每种密度都提供相关资源,比如我们可以为当前最主流的密度(xxhdpi)提供。但是当我们应用如果跑在其它像素密度屏幕上的时候,Android 系统是如何帮助进行适配的呢?你可以参考《Android drawable微技巧,你所不知道的 drawable 的那些细节》。系统会根据目标像素密度/资源所在的像素密度,得到一个值,然后根据这个值对图片资源宽高做缩放处理,以便达到在当前像素密度下最佳显示效果。

小结,附上屏幕相关概念思维导图:

屏幕相关概念

3、屏幕适配实践

屏幕适配实践

上面说了那么多,仅仅对屏幕适配涉及到的相关概念做了介绍。接下来就根据思维导图展开介绍屏幕适配的实践环节。

(1)wrap_content、match_parent、weight

尽量使用 wrap_content、match_parent 自适应尺寸,这个相信大家都已经用的很熟了,这里着重说下 weight,它只能在 LinerLayout 中使用(需要注意:使用 weight 的 LinerLayout 会有一定性能问题,主要发生在 measure 测量阶段):

下面通过一个简单的例子说明下,weight 在屏幕适配的作用,布局代码:

weight 示例

布局显示如下:

显示效果

我们修改上面的布局代码,调整为 TextView1 占权重 3,TextView2 占权重 1:

weight 示例

此时布局显示如下:

weight 显示效果

按照权重,我们将父布局分成 4 份,TextView1 占 3 份,TextView2 占 1 份,但实际效果显示却不是,这是为什么呢?

实际上 weight 属性的计算规则是:首先按照控件自身的尺寸进行分配,然后将剩尺寸再按照权重进行分配:

父布局宽度:200dp,TextView 1、2 宽度都是 50dp。此时剩余宽度:200 - 50 - 50 = 100dp。

剩余宽度按照 weight 进行分配:100 / 4 = 25dp,对剩余宽度划分为 4 份。

此时 TextView1 的宽度为:50 + 25 * 3 = 125dp。

TextView2 的实际宽度为:50 + 25 * 1 = 75dp。

计算公式:控件本身宽度 + (父布局宽度 - 所有子组件宽度之和) * (当前控件权重/总权重),以当前为例:

TextView2 的实际宽度:50 + (200 - 50 -50) * (1/4)= 75 dp。

(2)RelativeLayout

对于 RelativeLayout 的使用想必大家已经非常熟悉了,它主要基于控件之间的相对位置,在不同的手机屏幕上,控件的相对位置不会发生变化。

不过在 Android Studio 2.3 之后,Android 官方模板默认开始使用 ConstraintLayout 作为默认根布局。它的出现主要是为解决布局嵌套,以更加灵活的方式定位和调整控件位置,你可以参考《约束布局ConstraintLayout看这一篇文章就够了》。

(3)自动拉伸位图 .9.PGN 图片的使用(学名:Nine-Patch)

关于 .9 图的制作主要有两种方式:直接通过设计人员出图 .9图,这种方式我们再过多叙述。另外我们可以通过 Android Studio 完成一张普通图到 .9图。

我们通过 Android Sudio 先把一张图片重命名为 .9图片:

.9 图

然后通过 Android Studio 自带的编辑器预览:

Android studio 制作 .9 图

其实 .9图的制作主要分为两个步骤:

第一步:指定哪一部分可以被拉伸。

第二部,指定哪一部分不可以被拉伸。

当我们将鼠标放在图片上时,在图片的边缘会有一个宽度为 1dp 的像素点,这个像素点就是控制哪些部分是可以被拉伸,其余部分则是不可以被拉伸。感兴趣的朋友可以自己去体会下,但是大部分情况还是由设计人员直接提供(这是个精细活,需要慢慢调整)。

(4)最小宽度限定符(Smallest-Width)

Android 系统在 3.2 之后,我们可以通过最小宽度限定符为不同像素密度的设备进行适配,如:layout 文件 layout-sw480dp,values 文件:values-sw480dp,这里的 sw 代表的就是最小宽度(Smallest-Width,设备上最小的一边)。Android 系统会根据当前设备的像素密度去匹配最接近的资源目录。如下图提供 160dp 至 820dp 的 values 资源目录:

不同密度 values 目录

如下分别提供 7英寸(sw600dp) 与 10英寸(sw720dp) 平板的 layout 资源目录:

适合平板尺寸

最小宽度限定符(Smallest-Width)是目前主流的适配方案之一。

(5)使用布局别名进行兼容适配

使用布局别名方式,更多是为了兼容 Android 3.2 之前的设备。如果不需要这部分内容仅使用最小宽度限定符即可(Smallest-Width)。

布局别名说明

通过布局别名的方式,我们仅需要一个 layout 布局资源目录,此时可以通过不同的 values 资源目录进行适配:

不同密度布局

values-sw320dp 目录下 layout.xml 资源配置:

布局别名资源配置

values-sw480dp 目录下 layout.xml 资源配置:

布局别名资源配置

注意:name 名称需要保持一致,系统会根据目标设备密度自动匹配不同密度下 main 名称对应的布局文件,从而达到在不同像素密度适配不同的布局文件的效果。

(6)屏幕方向限定符(Orientation)

屏幕方向限定符

例如 values资源文件:values-sw600dp-land / values-sw600-port:

方向限定符说明

注意,我们通过 setContentView(R.layout.main),系统会自动根据当前屏幕方向选择 values-sw600dp-land 或 values-sw600dp-port 下分别对应的 main_land.xml 或 main_port.xml 布局文件。从而完成不同屏幕方向适配不同的布局文件目的。

实际开发过程中,可能对于使用屏幕方向限定符的需求场景还是相对较少的。

最后附上屏幕适配解决方案思维导图:

解决方案思维导图

总结

1、基于自适应尺寸的 wrap_content、match_parent,weight 避免固定的尺寸单位。

2、使用基于控件相对位置的 RelativeLayout,现在更加推荐使用 ConstraintLayout,对于布局性能以及扁平化更具有优势。

3、考虑使用自动拉伸的 .9 图或者使用矢量图,矢量图可以保证图片不受拉伸影响而导致失真。

4、限定符,包括:最小宽度限定符(Smallest-Width)、布局别名、屏幕方向限定符。

另外文中也给大家提到,例如今日头条通过修改系统 density 方式完成屏幕适配,不过任何一种方案都有利弊,具体你可以参考这里。它在帮助我们减少开发成本方面确实有很大帮助。很多人也有反馈到 DPI 的存在,就是为了让大屏显示更多的内容,如果大屏和小屏显示内容相同,那用户买大屏手机又有什么意义呢,我觉着大家对 DPI 的理解是对的,但是 Android 碎片化又太严重了,在需求和开发成本面前,还是各种各样,例如百分比的库层出不穷。鱼和熊掌不可兼得,DPI 的意义在 Google 的设计理念是完全正确的,但不是所有公司都能承受这个成本。

以上便是个人在屏幕适配方面探索实践的相关经验,文中如有不妥或其它更好的方案,欢迎您的指出!

如果喜欢我的文章,就请点个赞吧。

推荐阅读

一种非常好用的 Android 屏幕适配

骚年你的屏幕适配方式该升级了!-smallestWidth 限定符适配方案

更多相关文章

  1. Android的Activity屏幕切换动画(二)-左右滑动深入与实战
  2. android简易双屏支持
  3. android demo之ApiDemos下的text
  4. Android的Activity屏幕切换动画(二)-左右滑动深入与实战
  5. 利用HTML5开发Android笔记(上篇)
  6. Android屏幕密度(Density)和分辨率的关系
  7. android:layout_weight总有你不知道的用法.
  8. Android自适应不同屏幕几种方法
  9. Android屏幕完美适配最全攻略(最权威的官方适配指导)

随机推荐

  1. Dagger2 菜鸟学习指南
  2. Context 传递数据
  3. 为Activity屏幕的标题添加图标
  4. Android(安卓)4.4 Kitkat Phone工作流程
  5. android asmack 注册 登陆 聊天 多人聊天
  6. Android(安卓)FineCache NOSQL数据库
  7. Android项目开发第三周
  8. Fedora5下使用Sun JDK Netbeans乱码问题
  9. android graphview使用
  10. [Android] Debug Bridge(ADB) 技术实现(译