V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
tangpj
V2EX  ›  Android

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

  •  
  •   tangpj · 2018-10-07 23:08:00 +08:00 · 9814 次点击
    这是一个创建于 2240 天前的主题,其中的信息可能已经有所发展或是发生改变。

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

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

    原文链接: http://tangpj.com/2018/09/29/calces-screen/

    前言

    屏幕适配一直是移动端开发热议的问题,但是适配方案往往在实际开发的时候会和 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 的设计稿为例子,假如有下面这么一副设计稿,如果不进行任何适配的话,在不同的设备上的显示效果对比如下:

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

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

    这里有一点需要注意的是,可以看到第三台设备里面的适配还是有点问题,大概留下了 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 以下项目就可以看到生成的资源文件了,如下图:

    为了不影响编译时间 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。下面我们来看看转换效果:

    转换前

    转换后

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

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

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

    结语

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

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

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

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

    关于我

    如果你希望第一时间获得本人的最新文章的话,可以关注我的微信公众号:代码之外的程序员。或者关注我的博客: http://tangpj.com

    公众号二维码:

    qr

    2 条回复    2018-10-09 09:56:06 +08:00
    caijunyi
        1
    caijunyi  
       2018-10-09 00:14:40 +08:00   ❤️ 1
    武汉电信校园网你博客被劫持 请上 ssl
    tangpj
        2
    tangpj  
    OP
       2018-10-09 09:56:06 +08:00
    @caijunyi 感谢你的提醒,已经在上 ssl 了。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   4513 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 23ms · UTC 01:09 · PVG 09:09 · LAX 17:09 · JFK 20:09
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.