【设计探索】从设计师的角度看Android屏幕适配

前言

在移动端的产品迭代中总会碰到屏幕适配的问题,不同的尺寸的设备展示的内容不一样,在Android平台中这个现象更为突出。而它是怎么造成的,这个时候该如何解决屏幕适配的问题?

作为一名设计师,我将从程序员的角度去分析一下其产生的原因,以及该如何更好地解决这个问题。本篇文章将作为一个引子,后续将会对里面的内容加以完善、补充。

屏幕展示什么内容,展示多少内容很多时候并不是UI设计师来定的,或是产品经理、或是交互设计师,为了方便阐述,下面将他们统称为设计师。

设计长度单位

在移动端的开发中,我们会碰到很多个长度单位:

  • Web中为px(pixel)。
  • iOS为pt(point)
  • Android中为dp(Density-independent pixels)、sp(Scale-independent pixels)

其中px为像素,是最小展示单位,放在显示器中对应着一个发光像素点,此时可以理解为物理像素 ,其中1080 × 1920的屏幕则拥有2073600个像素点,为宽高相乘的结果。但是像素并非都代表物理像素,还可以作为渲染像素。在部分手机中,这两者大小不一致,渲染像素会经过一次转换才在物理像素中展示。

iPhone 8 Plus的物理像素为1080p,但是渲染像素为 1242 * 2208,其截图的尺寸和展示的尺寸不一致。

而pt、dp、sp的概念是相似的,都是逻辑像素 。在实际展示中,会经过一定的比例缩放来映射到像素点中,即刚刚提到的px,而这一个比例也称为缩放因子(scale factor)。

但是在UI设计中的长度单位是什么呢?我们打开设计软件看一下:

答案是没有单位

被模糊掉的长度单位

UI设计软件中模糊了长度单位。目的也很明确,就是让设计师不要在意自己使用的是什么长度单位,不用特意去了解不同长度单位之间有什么关系。

  • 在移动端或Mac的预设中,它是逻辑像素作为单位
  • 在PC端和Web的预设中,它是物理像素px作为单位

由于UI设计软件是矢量图,并且逻辑像素在展示过程中会自动缩放到物理像素并展示, 在移动端设计稿中使用一倍设计稿 (预设)交付给开发即可无痛适配二、三甚至四倍设计稿。

在iOS开发中,SwiftUI也特意模糊掉了单位,举个例子:

swift 复制代码
ZStack {
    Text(title).font(.system(size: 50))
}.frame(
    width: 200
    height: 400
)

而它的作用也与前者相似,减少开发者的心智负担,iOS开发工程师直接使用设计师交付过来的尺寸即可完美适配

在Flutter中也是如此:

dart 复制代码
Container(
  width: 100,
  height: 50,
)

甚至在ReactNative中也是类似的:

javascript 复制代码
const App = () => (
  <View width={100} height={50}>
    <Text>Hello, world!</Text>
  </View>
);

Android"灵活"的单位选择

在Android中非常灵活,你通常可以很轻易地接触到不同的单位并做出选择,一不注意容易埋下坑。如果使用的逻辑像素,系统内部在展示过程会将逻辑像素(dp、sp)转换成实际渲染像素(px),如果使用的是渲染像素,系统将会1:1渲染。

kotlin 复制代码
// Compose 其实对长度单位封装得很完善了
Text(
    text = text,
    fontSize = 12.sp,
    modifier = Modifier.width(200.dp).height(100.dp)
)
// 而在 View 中比较容易混淆
TextView(context).apply {
    textSize = 14F // 其实是14.sp
    updateLayoutParams { 
        width = 120 // 其实是px
        height = 120 // 也是px
    }
}

Android的灵活性也为后续提到的屏幕适配方案提供了基础。

屏幕适配工作的诞生

设计稿尺寸的选择

要看到问题的本质,就得追根溯源。市面上的Android设备五花八门,DPI也各不相同,这也会间接造成屏幕的逻辑像素非常多且杂,可能会有尺寸相同的设备展示的内容不一致,不过好在大多数设备的展示比例都还算正常。但是,反观iPhone的逻辑像素却非常稳定,新的设备和旧的设备的逻辑像素变化不会很大,甚至很多年都不会有改变。设计师在定设计稿尺寸的时候大多数都会首选iPhone的逻辑像素作为底图而不是Android的。

看到这里肯定会有疑问,iPhone不是一直在变长吗?

但是仔细想一下,手机的长度真的很影响到APP的内容展示和交互适配吗?一般内容超出屏幕的场景几乎都是列表或者信息流,而这两者都会用上滚动布局。只要用上滚动布局之后,手机的长度对于设计来说并不敏感,无论长一些亦或者短一些,对于用户体验来说只是多划一下或者少划一下,因此UI适配主要看的是逻辑宽度

我们来回顾从iPhone 4到iPhone 11这整整9年间,逻辑宽度并没有产生太大变化。简单概括为三个宽度:

  • 320(iPhone 4,5 / iPhone SE)
  • 375(iPhone 标准版 / iPhone Pro)
  • 414(iPhone Plus / Max)

这三个宽度应该选哪个呢?大家应该很熟悉,也就是375

至于为什么不是前后两个,仔细想想,当你选择320作为设计稿尺寸的时候,这张设计稿放到414宽度中将会有非常大的留白,并且能展示的内容非常少,显而易见320并不是很好的选择。

当你选择414作为设计稿尺寸的时候,一旦APP运行在320尺寸的手机中,将会有非常多的内容被裁剪或者看不到,甚至对于开发来说容易出现BUG。

所以折中选择中间的尺寸会是一个比较好的选择,遵循一个这样的设计观念会对屏幕适配非常有帮助:在设计过程中注意对设计稿进行适当的留白,以保证小尺寸的设备展示不会太奇怪。

从iPhone12开始这三个宽度将会有一些变化,320宽度被苹果抛弃,375成为最小的尺寸。如下所示:

  • 375(iPhone mini / iPhone SE)
  • 390 / 393(iPhone 标准版 / iPhone Pro)
  • 428 / 430(iPhone Plus / Max)

也就是说从这几年开始,需要考虑从375宽度的设计稿迁移到390 / 393了,以达到较好的设计效果。若设计师还在使用375作为标准设计宽度的话,就会经常发现实际展示和设计稿对不上,这时的问题就出在设计师的设计底图尺寸选择上了。

Android阵营适配iPhone设计稿

由于iPhone的屏幕宽度数量很少,每个设计阶段只需要考虑三种宽度即可,因此iPhone的屏幕适配问题寥寥无几。以iPhone的宽度作为标准的设计稿在Android阵营中总会有些水土不服,Android这边实现出来的效果不是偏大就是偏小。在设计走查环节,设计师眉头紧皱,Android开发工程师苦不堪言。

于是Android开发工程师利用Android的灵活性和可自定义性发现了很多能"还原"iPhone设计稿的方法。

作为一名Android开发,我这边聊一下熟悉的Android领域。(不太了解iOS)

SmallestWidth适配方案

既然设计师们使用特定的宽度作为设计稿宽度,那我们就可以以这个宽度去定义一个新的"长度单位"。而一旦使用这个单位,就可以直接无脑使用设计师交付的设计稿中的尺寸来开发,做出来的效果绝对满足设计稿的展示效果。

新"长度单位"这个概念只是方便理解,并不是真的新单位。

我们以390宽度举例,既然在390宽度的手机能够完美还原设计稿,也就是说在390宽度的手机直接使用dp即可,即1长度单位 = 1dp。并以此向更窄以及更宽的手机去扩展。

  • 在宽度为360的手机1长度单位 = 360 ÷ 375 ≈ 0.92dp
  • 在宽度为400的手机1长度单位 = 400 ÷ 375 ≈ 1.03dp。

而Android也支持自动在不同的尺寸去选择不同的资源文件,因此我们可以创建不同的资源文件放在对应的文件夹,如下所示。

我们在布局文件直接使用即可。

xml 复制代码
<TextView
    android:layout_width="@dimen/DIMEN_1"
    android:layout_height="@dimen/DIMEN_1" />

在代码中也可以动态获取

kotlin 复制代码
textView.updateLayoutParams { 
    width = resources.getDimension(R.dimen.DIMEN_1).toInt()
}

只要项目中的视图都使用dimens.xml中的对应长度,就可以完美适配设计稿,它的类似下图所示:

中图是390的设备展示效果,而左图是宽度为360的设备展示效果,右图是宽度为440的设备的展示效果。他们就好像等比缩放了一样,而事实也是如此,这种效果完美符合设计师的预期了。

Density适配

逻辑像素经过一定的缩放比例映射到实际渲染像素,而这个比例被称为缩放因子,它与DPI的转换关系为:
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> D P I = 160 × S c a l e F a c t o r DPI = 160 \times ScaleFactor </math>DPI=160×ScaleFactor

缩放因子在Android中的名字为Density,可以通过如下代码获取,而DPI为densityDpi。

DPI和PPI的概念相似,但是其实有些不一样,PPI指的是屏幕的像素密度,DPI是Android特有的像素密度。

kotlin 复制代码
val displayMetrics: DisplayMetrics = resources.displayMetrics
val density = displayMetrics.density
val densityDpi = displayMetrics.densityDpi

而最抵死的是,DisplayMetrics这个类中的属性都是可以修改的,并且真的可以影响到系统展示。那我们在视图展示之前把里面的参数按照标准设计稿去进行调整就好了。

kotlin 复制代码
displayMetrics.densityDpi = displayMetrics.widthPixels.toFloat() / 390F
displayMetrics.density = (targetDensity * 160).toInt()

在实际开发中使用dp进行开发即可。

为了简洁,此处未考虑字体缩放sp。

它的效果和上面的那种方式是一样的,类似等比缩放。

等比缩放的问题

大家发现了吗?常用的屏幕适配方式都是将当前设备展示的内容等比缩放 到设计师出的设计稿底图的比例,已达到展示的内容与设计稿一致,在水平方向,不会多展示东西,也不会少展示东西,满足设计需求的同时也不会产生更多的BUG。

但是我个人并不推荐大家继续使用,容易为以后埋下更深的坑,为什么?Android设备并不止有一种尺寸,它有折叠屏横竖屏,有平板,有车机,甚至手机横过来也是一种尺寸。举个例子上面适配好的页面一旦放在平板或折叠屏中展示时会变成以下这样:

在屏幕宽度较大的设备中,所有东西都变得巨大,无法正常使用。

如果使用以上两种屏幕适配方式,你只能祈祷用户不要使用大屏幕的设备,但是这并不可能。

一旦使用上方等比缩放的方式,将会为后续适配埋下更多坑。例如:

  1. 设计师修改设计稿的标准宽度将非常困难,难以推进。因为一旦修改了,将会影响到旧的页面的展示效果,影响到代码的复用。
  2. 一旦在大屏设备使用APP,将难以使用。
  3. 一旦页面设计太多逻辑,需要进行屏幕适配,只能大规模重构,这将耗费大量人力。

有没有更好的方式?

需要彻底解决屏幕适配这个问题,我们先捋一下思路。

  1. 设计师以iPhone的宽度作为基准制作设计稿,考虑了iPhone的三个不同宽度展示的内容,最大和最小宽度为设计稿的可用宽度范围 。 由于iPhone的屏幕尺寸区别别较小,可能设计时没有特别考虑也可以适配。
  2. 由于Android设备的DPI各不相同,所以可能出现超出设计稿的可用宽度范围的手机,会出现漏展示或留白较多的问题,也可能会出现尺寸相同的设备展示的内容不一致(DPI和PPI不统一)。但是要求所有Android产商统一DPI不太现实。

鉴于Android视图开发的灵活性和还原设计稿的要求,Android开发者主动往前迈了一步,找到了属于自己的方式,把Android APP的尺寸变成了iPhone的形状。这既让设计师少走一步,又完美还原了设计稿,进一步减少了BUG。

随着手机屏幕在逐渐升级,千元机也能用上1080P的屏幕,540p、720p屏幕慢慢减少,奇怪的ppi也慢慢变少。厂商在设计手机的时候也会将手机的DPI设计在一个比较合理的范围内。

因此,在我看来,屏幕适配这种本来应该Android开发考虑的事情,应该慢慢地交接给产品经理、交互设计师、UI设计师了。不同屏幕该展示什么内容,主动去适配不同PPI / DPI的设备、不同宽度的设备、不同比例的设备交给设计师们去构思,而程序员可以在开发过程中进行兜底、规避展示不全或者留白过多的问题。

设计师们需要考虑的事情

尽量多适配不同大小的逻辑宽度

主动把宽度的适配范围下潜,一般下潜到320左右(折叠屏单屏宽度)即可,因此在设计的时候需要预留更多的空间,或者在宽度较小的设备展示更少的内容。

把宽度更高的设备适配提上日程,使用该应用的未必是手机,可能是更多大屏设备。而宽度更多的设备可能会有留白,这对于设计来说是比较糟糕的,因此在产品设计的时候可以往里面塞下更多内容,以达到更好地利用屏幕空间的目的。一旦屏幕宽度达到一定的程度,就需要考虑使用不同的设计稿了。

关于这两点谷歌官方有更好的阐述,因此可以查看:developer.android.com/guide/topic...

以下是《小鹅事务所》记账模块中的一个简单的例子,在出设计稿的时候需要同时考虑多个尺寸应该展示什么,根据屏幕尺寸对内容有增有减,并对布局位置进行相应调整,以达到比较好的展示效果。

多窗口适配

在大屏设备使用软件时,我们经常会看到软件被分割成两半,左边浏览信息流,右边查看详情,两边都是单独的页面。这是一种比较简单的大屏适配方式,在逻辑复杂,无法马上适配大屏页面的情况下,使用这种方式也能达到一个不错的适配效果,例如下图所示。

减少强迫症

在设计走查阶段,如果Android设备没有使用等比缩放去适配设计稿的方式,而是单纯使用逻辑像素进行开发,就可能会发现间距、布局大小和设计稿有些出入。

这个时候就不需要截屏到设计软件中拿尺子量啦,尊重设备的差异 ,只要实际偏差不太离谱,评估过不影响使用就可以睁一只眼闭一只眼。若实在太过严重,则可以考虑一下特殊对该机型的DPI重新编排布局或调整交互方式,即上面描述的适配该类型的屏幕。因为涉及到一定的工作量,因此是否需要重新编排布局这项工作也需要进一步评估。

目前绝大多数手机的DPI都比较合理,因此需要特殊适配的布局应该会很少。

总结

本文以屏幕适配为起点,对当下Android常见的屏幕适配问题简单做了一个阐述。

  1. 简单介绍了UI设计的长度单位,还有设计稿选择375宽度的原因。
  2. Android设备的展示效果为什么总是和设计稿有出入。
  3. Android当下比较经典的屏幕适配方式的优缺点。
  4. 屏幕适配未来的工作需要慢慢交接给设计师们。

看完这篇文章,大家内心肯定还会有一些问题,而我后续也会对文中表述不清晰的地方另外编写文章进一步讲解,同时《小鹅事务所》开源项目适配大屏设备也提上日程,后续文章将辅以实践讲述。

参考

相关推荐
忘忧记5 小时前
Python MySQL SQLServer操作
android
万岳科技系统开发5 小时前
外卖系统开发实战:从架构设计到代码实现
android·小程序·开源
standxy6 小时前
通过轻易云平台实现聚水潭数据高效集成到MySQL的技术方案
android·数据库·mysql
he_wen_jian8 小时前
Android 项目引入gradle Connect timed out
android
哎呦没9 小时前
企业OA管理系统:Spring Boot技术实现与案例研究
android·spring boot·后端
李新_10 小时前
一文聊聊Flutter多业务混合工程实践
android·flutter
weixin_SAG13 小时前
7天掌握SQL - 第三天:MySQL实践与索引优化
android·sql·mysql
疯狂的皮卡15 小时前
【安卓脚本】Android工程中文硬编码抽取
android
菜鸟、小高16 小时前
从0开始学PHP面向对象内容之常用设计模式(适配器,桥接,装饰器)
android·设计模式·php
找藉口是失败者的习惯17 小时前
Jetpack Compose 生命周期介绍
android·ui