给你一个全自动的屏幕适配方案(基于SW方案)!—— 解放你和UI的双手

image

Calces系列相关文章:Calces自动实现Android组件化模块构建

前言

屏幕适配一直是移动端开发热议的问题,但是适配方案往往在实际开发的时候会和UI提供的设计稿冲突。本文主要是基于官方推荐的配置限定符方案(Smallest Width目前Android屏幕适配的最优方案)来实现一个接近完美的屏幕适配方案。

原创声明: 该文章为原创文章,未经博主同意严禁转载。

对于完美的适配方案笔者是这样定义的:

  1. 能完美适配UI稿。
  2. 适配完毕后,在高清设备上不会出现模糊的现象。
  3. 尽量减少对项目的侵入性。

下面我会从屏幕适配的一些基础知识入手,向你慢慢展现一个最优的屏幕适配方案。

这是我写的Android构建辅助插件库,其中的Screen插件是实现自动屏幕适配的关键。因为怕大家错过这个插件,所以在这里提前推荐给大家。

Screen插件主要提供两个功能:

  1. 配置设计稿密度与需要适配屏幕的Smallest Width值来自动生成对应的资源文件
  2. 提供需要的最高清的位图,根据需要缩放的密度自动缩放位图资源。

Github: 如果觉得这个工具对您有帮助的话,可以点下Star,这是我坚持下去的动力💪

如果要深入了解这个插件是如何自动帮你实现屏幕适配的,请仔细研读下文。

本文的Demo地址:https://github.com/Tangpj/Android-advanced-blueprint。项目中的ScreenAdaptation就是本文的Demo。

屏幕适配概览

概念

  • 屏幕尺寸: 屏幕尺寸是指屏幕的物理尺寸,是通过测量屏幕的对角线测量出来的。

  • 屏幕密度: 屏幕物理区域中的像素量,通常称为dpi(每英寸的像素点数)。密度越高,现实效果越好。

  • 分辨率: 屏幕上物理像素的总数。在进行屏幕适配时,不要直接通过分辨率适配,应该通过屏幕尺寸和屏幕密度来适配

  • dp: dp是Android特有的虚拟像素单位,与物理参数无关。1dp等于160 dpi屏幕上的一个物理像素,在运行时,系统 根据使用中屏幕的实际密度按需要以透明方式处理 dp 单位的任何缩放 。dp 单位转换为屏幕像素很简单: px = dp * (dpi / 160)。在 240 dpi 屏幕上,1 dp 等于 1.5 物理像素。

如何支持多种屏幕

Android支持多种屏幕的基础是它能够针对当前屏幕的配置,以适当的方式渲染应用的布局和位图,这是由系统层面提供的支持。我们可以通过以下方式来更好地处理不同屏幕配置的适配:

  1. 为不同的屏幕尺寸提供不同的布局
    默认情况下,Android会调整应用的布局大小以适应当前设备的屏幕,大多数情况下系统提供的支持就能满足我们的需要。但是有时候需要针对不同的屏幕分辨率来设计不同的布局,以达到更好的现实效果。
  2. 为不同的屏幕密度提供不同的图片资源
    我们可以通过配置密度资源的配置限定符来提供不同像素的图片,来适配不同的屏幕密度。

对于第一点,在实际工作中是很难实现的。因为一般UI只会提供一套设计稿,不会根据不同分辨率的屏幕来提供相应的适配。但是我们没办法控制我们的App最终会运行在什么分辨率的屏幕上,为了达到在不同屏幕上的显示效果一直,我们可以通过提供不同密度的位图资源与Smallest Width方案来实现屏幕适配。

什么是Smallest Width适配

Smallest Width字面上的意思就是最小宽度,由可用屏幕区域的最小尺寸指定。 具体来说,设备的 smallestWidth 是屏幕可用高度和宽度的最小尺寸。

例如,如果布局要求屏幕区域的最小尺寸始终至少为 600 dp,则可使用此限定符创建布局资源 res/layout-sw600dp/。仅当可用屏幕的最小尺寸至少为 600dp 时,系统才会使用这些资源,而不考虑 600dp 所代表的边是用户所认为的高度还是宽度。smallestWidth 是设备的固定屏幕尺寸特性;设备的 smallestWidth 不会随屏幕方向的变化而改变

所以我们可以根据需要适配的屏幕的sw值来提供不同的资源来实现屏幕适配。

UI设计与屏幕适配的一些基础理念

我觉得很多屏幕适配教程都漏了一个很重要的点,就是:没有解释清楚屏幕适配与UI设计之间的关系!

一般在实际开发的时候,UI设计师都会提供一套UI稿与标尺,工程师是通过这套标尺来开发UI的。UI如果我们要做好Android的屏幕适配,那么我们必须要明白的一点就是,UI稿在我们进行界面开发中是充当锚点的作用的。要适配其它的屏幕的话,必须要以这个基准为基础计算其它屏幕的dimens资源的值。

举个例子:
例如,很多UI设计师都会以iPhone6的尺寸作为标准来制作设计稿与标尺的,而iPhone6的屏幕宽度为375px,所以这个宽度为375px的设计稿就是我们屏幕适配的基准了。

假设有一台sw等于375dp的设备的话,那么这个设备与设计稿对应的关系就是1dp = 1px,那么我们就不需要进行任何适配,直接把设计稿以px为单位的标尺值以1:1的比例转换成以dp为单位就可以了。

在这里,我们可以得出一个结论就是:屏幕适配需要以UI稿为基准再制定合适的适配方案!

但是有一个问题就是,每个UI设计师的喜好都是不一样的,提供的设计稿的比例尺也不是固定的。而且Android的屏幕碎片化非常严重,我们需要适配的屏幕的sw的值也是变化多端的。所以如果每次都需要手动计算对应的dimens值的话,非常耗时间与繁琐。网上提供了一些工具来快速生成对应sw的dimens值,但是这些工具都会存在两个缺点:

  1. 没办法根据UI设计稿来转换,所以不一定能100%还原设计稿效果

  2. 会生成大量无用的dimens值。其实如果我们细心观察过设计稿的话,我们会发现,其实每份设计稿常用的px值都是固定的十来个。例如同样以375px的设计稿为基准的话,使用工具会生成1px ~ 375px对应的dp值,所以会存在大量的无用dimens值。这样只会徒增安装包的大小。

这个两个缺点,可以使用笔者的calces.screen插件来解决,下文会介绍这个插件的使用方法与使用效果的。

使用calces.screen快速实现Smalles Widths适配方案

适配前与适配后对比情况

还是以iPhone6的设计稿为例子,假如有下面这么一副设计稿,如果不进行任何适配的话,在不同的设备上的显示效果对比如下:

image

第一个手机就是上文中说到的sw = 375dp的手机,我们可以看到sw为其他值的手机上面,显示效果都不如意。在sw = 411dp和sw = 900dp的设备上,都留有大量的空白空间,而在sw = 360dp的设备上,则有超出屏幕范围的现象。我们适配的目标就是:达到所有设备上显示的效果都和设计稿(sw = 375dp上的效果)一致。

使用calces.screen插件适配后的效果如图所示:

image

这里有一点需要注意的是,可以看到第三台设备里面的适配还是有点问题,大概留下了1dp左右的白边。这个是pixel 2 XL的模拟器,可以看到,测量出来的sw值应该是411dp的,但是经过笔者的实际测量,发现sw应该是412dp才对。有兴趣的读者可以自己在布局编辑器里面创建一个width为411dp的控件,可以看到在pixel 2 XL设备下也是有大概1dp的白边的。所以这个1dp的误差应该是和设备有关的,这里贴上用calces.screen生成的sw = 411dp的dimens文件的值观大家参考。

<resources>
  <!-- sw411dp -->
  <dimen name='px_48'>53dp</dimen>
  <dimen name='px_75'>83dp</dimen>
  <dimen name='px_100'>110dp</dimen>
  <dimen name='px_125'>137dp</dimen>
  <dimen name='px_150'>165dp</dimen>
  <dimen name='px_200'>220dp</dimen>
  <dimen name='px_250'>274dp</dimen>
  <dimen name='px_300'>329dp</dimen>
  <dimen name='px_375'>411dp</dimen>
  <dimen name='text_px_28'>31sp</dimen>
  <dimen name='text_px_32'>36sp</dimen>
  <dimen name='text_px_40'>44sp</dimen>
</resources>

当sw = 411dp 时,px_375的实际值时411dp,所以这是符合我们的预期转换结果的。

如何引入calces.screen

首先,我们需要引入calces插件,引入的方式很简单:

在项目的build.gradle中添加代码:

//Gradle版本高于2.1的情况下(推荐方案)
plugins {
    id "calces.screen" version "1.2.3"
}

//Gradle版本低于2.1的情况下(2.1以上版本也兼容这种方式)
buildscript {
  repositories {
    maven {
      url "https://plugins.gradle.org/m2/"
    }
  }
  dependencies {
    classpath "gradle.plugin.com.tangpj.tools:calces:1.2.3"
  }
}

在modules的build.gradle中添加代码:

apply plugin: "calces.appconfig"

使用calces.screen适配屏幕

首先,我们需要在res/values/文件夹中创建dimens.xml文件,然后按照设计稿的标尺把需要用到的尺寸写到该文件下。例如:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <!--design 375px-->
    <dimen name="px_48">48dp</dimen>
    <dimen name="px_75">75dp</dimen>
    <dimen name="px_100">100dp</dimen>
    <dimen name="px_125">125dp</dimen>
    <dimen name="px_150">150dp</dimen>
    <dimen name="px_200">200dp</dimen>
    <dimen name="px_250">250dp</dimen>
    <dimen name="px_300">300dp</dimen>
    <dimen name="px_375">375dp</dimen>

    <!--text size-->
    <dimen name="text_px_28">28sp</dimen>
    <dimen name="text_px_32">32sp</dimen>
    <dimen name="text_px_40">40sp</dimen>
</resources>

这就是我们的基准dimens文件。

现在我们只需要把基准尺寸与需要适配的尺寸通过Gradle配置就可以了,例如,上面的例子中,我们需要适配的sw有:320dp, 411dp, 900dp,那么我们需要在modules的build.gradle文件下添加如下代码:

screen{

    dimens{
        designPx 375
        smallesWidths 320,375,411,900
        scale BigDecimal.ROUND_UP
        auto true
    }

}

上面配置信息的对应关系是:

  • designPx:设计稿的sw尺寸(单位px)

  • smallesWidths:需要适配的屏幕sw尺寸(单位dp)

  • scale: 数字取整的方式
    因为Android系统只能适配整数单位的dp值,所以我们可以通过scale来配置具体的取正方式。这里直接取BigDecimal提供的round来实现。如果不设置的话,则会生成double类型的dp值(实际使用的时候会丢弃小数位)

  • auto:是否自动生成dimens,当auto为true时,每次build都会重新生成一次适配dimens文件。
    如果不设置auto或设置为false的话,可以手动调用gradle任务来生成。
    调用命令: /gradlew dimensCovert
    也可以直接点击gradle任务执行,方式如下图:

配置完毕后,重新build以下项目就可以看到生成的资源文件了,如下图:

image

为了不影响编译时间auto建议设置为false,需要的时候再手动启动任务生成适配资源文件。

如何确定我们需要适配什么sw值?

除了自动生成sw外,我们还需要确定,我们的App需要支持那些sw值。最简单的方法就是,先确定我们要支持哪些设备。这里笔者给出一个建议就是,市面上有非常多设备的sw值都是360dp的,所以我们必须要适配360dp的设备。至于其它的设备,我们可以这样来确定,在开发者模式里面找到一项叫做最小宽度的参数,里面的值就是我们需要的sw值。具体如下图:

Nexus S sw值

例如,上面这个是Nexus S的sw值。如果我们不专门适配sw = 384dp的屏幕的话,那么系统就会默认寻找低于384dp的适配资源(所以360dp是一个相对通用的适配值)。当我们拥有测试设备的时候,使用calces.screen适配是非常简单的。那么如果我们不知道没有测试设备呢?(例如有用户反馈,某个设备下的适配有很大问题)

这里给大家推荐一个网站:Device Metrics

这个网站是Material Design的设备参数查找网站,用户在这里直接找到对应设备的尺寸就可以了(之前的方法翻车了,溜了溜了)。

一般情况下,sw为360dp和480dp的屏幕会比较常见,所以我们必须要生成这两套资源,如果需要支持Pad的话,则需要适配sw = 600dp 或 sw = 720dp的屏幕,然后再根据实际情况适配其它sw值的屏幕。

到这里为止,我们就完成了Android基于sw方案的屏幕适配了,非常简单!

但是,本文还没结束,这个插件除了提供自动实现基于sw方案的适配外,还提供了一个杀手级功能:根据配置自动把生成对应分辨率的位图资源。当我们需要适配多种不同屏幕密度的手机的时候,只需要提供一套高清位图资源就可以了,解放你和UI设计师的双手。

calces.screen实现位图自动缩放适配

为不同密度的屏幕提供不同的位图资源是Google官方推荐的屏幕适配做法。这样做的好处是,能使App在不同密度的屏幕上都能达到最好的效果,不会出现在高清屏下出现老年机的显示效果,并且在不同密度的屏幕下都能保持相对稳定的显示效果。下面是位图资源密度对应的比例关系:

密度限定符 比例关系 说明
ldpi 0.75 适用于低密度屏幕 (~120dpi) 的资源
mdpi 1 适用于中密度屏幕 (~160dpi) 的资源(基线密度)
hdpi 1.5 适用于高密度屏幕 (~240dpi) 的资源
xhdpi 2 适用于超高密度屏幕 (~320dpi) 的资源
xxhdpi 3 适用于超超高密度屏幕 (~480dpi) 的资源
xxxhdpi 4 适用于超超超高密度屏幕 (~640dpi) 的资源。此限定符仅适用于 启动器图标。

但是这里会产生一个问题,一般情况下,位图资源是UI设计师提供给我们的。我和很多UI设计师讨论过,他们的方案就是先切一套最高清的图片,然后再根据需要进行缩放,然后提供给工程师使用。

一般情况下,这种做法除了繁琐点也没什么问题。但是如果现在出现了一个情况,就是需要支持更低密度的屏幕呢?这种情况只能让UI设计师再缩放一套密度的位图。那如果某部分位图已经不再使用了,需要删除呢?那工程师需要把其它密度的位图找出来再删除。而且再往工程里面添加新的位图的时候也需要手工添加。

所以一般情况下,UI提供图片资源 —— 工程师使用图片资源这个过程中是纯手工控制的。工作非常繁琐并且没什么意义,而且手动迁移的过程中还非常容易出错(想想如果复制漏了某几个密度的位图资源会是什么画面?)。所以calces.screen还提供了位图管理功能。

calces.screen管理位图

使用Screen的位图缩放功能之前,先和设计师/产品商量好App最高需要支持哪个密度的屏幕。然后设计师以后只需要提供这套密度的位图就可以了。之后我们只需要在modules的build.gradle中进行配置,配置方式如下:

screen{
    mipmap{
        designDensity "xxxhdpi" //测试用,目前手机屏幕最高只支持到xxhdpi
        mipmapDensity 'xxhdpi','xhdpi','hdpi','mdpi'
        auto true
    }

}

配置完之后,重新build文件就可以了,当然不希望增加编译时间的话,可以把auto置为false或者不设置。mipmap支持增量编译功能,只会对文件夹中不存在的位图进行缩放,已存在则跳过,识别条件是文件名。手动启动位图缩放功能的方式和上述方式一致,任务名称是mipmapZoom。下面我们来看看转换效果:

转换前

image

转换后

image

读者可以点进去查看一下转换后的图片尺寸,可以发现,转换后的图片符合我们需要的的比例。有兴趣的读者可以下载demo,把其它分辨率的位图资源删除,通过mipmapZoom任务重新生成。

通过mipmapZoom任务,可以大大减少UI设计师与工程师的工作量,只需要管理一套位图文件即可,把我们从机械化的任务中解放出来。

注:目前版本不支持位图删除功能,所以当我们需要删除部分位图的时候,需要把自动生成的图片文件全部删除,重新生成,后续版本会增加该功能。

结语

屏幕适配一直是移动端开发工程师的一大难题,面对琳琅满目的屏幕尺寸与屏幕密度,我们一直在找一个更好的适配方案。Smallest Width是目前Android中最简单最好用的适配方案,没有之一,它是由系统提供支持的,并且在适配时不会因为屏幕分辨率与设计稿的差异过大造成一些奇奇怪怪的问题(大屏幕上面变糊,小屏幕又显得像素过于密集)。笔者这个适配方案是基于Smallest Width与提供多套位图为基础,通过Gradle插件来自动处理sw比例计算与文件生成、位图自动缩放来实现一个相对更好的适配方案。

calces.screen开发的初衷时简化UI设计师与Android工程师的工作量,目前已经基本达城这一目标。

好了,关于calces.screen插件的介绍就到此为止了,这里再一次提醒大家,如果觉得calces对你有所帮助的话,可以点下star,鼓励下作者。如果有一些更好的想法的话,可以参与这一开源项目。笔者会一直维护这个项目的,致力于减轻Android工程师的负担,把重复的机械性工作全部交给Gradle来处理。

Github: 如果觉得这个工具对您有帮助的话,可以点下Star,这是我坚持下去的动力💪

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容