阅读 225

[译] 在 Android 应用中使用矢量资源

Illustration by [Virginia Poltrack](https://twitter.com/VPoltrack)

在之前的文章中,我们研究了 Android 的 VectorDrawable 图像格式以及它能够实现的功能:

在这篇文章中,我们将会深入研究如何在你的 app 中应用这些矢量资源。VectorDrawable 是在 Lollipop(API 21)中引入的,也可以在 AndroidX 中使用(作为 VectorDrawableCompat),可以向下兼容到 API 14(这使其可以覆盖超过 99% 的设备)。本文将概述一些能真正在你的应用中使用 VectorDrawables 的建议。

首先是 AndroidX

从 Lollipop 开始,你可以在任何需要使用其他可绘制类型的地方使用 VectorDrawables(使用标准的 @drawable/foo 语法引用它们),但是我建议始终使用 AndroidX 实现。

这会显著增加其使用平台的范围,不仅如此,它还支持将特性和 bug 修复程序向后移植到旧平台。例如,使用 AndroidX 中的 VectorDrawableCompat 可以:

  • nonZeroevenOdd 路径 fillTypes —— 定义形状“内部”的两种常见方法,通常用于 SVGs(evenOdd 在 API 24 中得以实现)
  • 渐变(Gradient)& ColorStateList 填充 / 画笔(在 API 24 中被添加实现)
  • Bug修复

事实上,AndroidX 将使用 compat 实现,甚至在一些存在本地实现的平台上(当前是 api 21-23)也可以实现上述优点。否则,它将委托给平台实现,因此仍然可以接收对新版本的任何改进(例如,为了提高性能,VectorDrawable 在 API 24 的 C 中重新实现)。

基于这些原因,你应该始终使用 AndroidX,即使你很幸运地将你的 minSdkVersion 设置成 24。这没什么不好的,如果/当 VectorDrawable 在未来扩展了新的功能,并且它们也被添加到 AndroidX 中,那么它们就可以直接使用,而不需要重新检查代码。

Alex Lockwood 是这么说的

怎么使用?

为了使用 AndroidX 矢量支持(AndroidX vector support),你需要做 2 件事情:

1. 开启支持

您需要在应用的 build.gradle 中选择加入 AndroidX 矢量支持:

android {
    defaultConfig {
        vectorDrawables.useSupportLibrary = true
    }
}
复制代码

如果 minSdkVersion < 21,这意味着 Android Gradle 插件无法生成矢量资源的 PNG 版本 —— 如果我们使用 AndroidX 库的话就不用担心这个问题。

通过默认的 AAPT(Android 资产包装工具)版本资源。它也被传递给构建工具链。这意味着,如果你在 res/drawable/ 中声明一个 VectorDrawable,它会为你将其自动移动到 res/drawable-v21/,因为系统知道这就是 VectorDrawable 类被引入的时候。

这可以防止属性 ID 冲突 —— 在 VectorDrawables 中使用的属性(android:pathDataandroid:fillColor 等)都有一个整数 ID,这些 ID 是在 API 21 中添加的。在老版本的 Android 上,没有任何东西可以阻止 OEM 使用任何"无人认领”的 ID,因此在较老的平台上使用较新的属性是不安全的。

这种版本控制将阻止在较老的平台上访问这些资源,使反编译成为不可能的事情 —— gradle 标志禁用了可绘制对象资源(vector drawables)的版本控制。这就是为什么你使用 android:pathData 引入你的向量而不是必须切换到 app:pathData 等其他后移功能。

2. 使用 AndroidX 加载

当加载 drawables 时,你需要使用 AndroidX 的方法,因为它已经提供了对矢量资源的支持。这个的切入点是始终利用 AppCompatResources.getDrawable 加载 drawables。虽然有许多方法可以加载 drawables(因为某些原因),但是如果你想使用 compat 向量,就必须使用 AppCompatResources。如果你做不到这一点,那么你就不能连接到 AndroidX 代码路径,当你尝试使用任何你运行的平台不支持的功能时,你的应用程序可能会崩溃。

VectorDrawableCompat 还提供了一个 create 方法。 我总是会建议使用 AppCompatResources,因为这会增加一层缓存。

如果你想以声明的方式设置 drawables(即在你的布局中),appcompat 提供了一些 Compat 属性,你应该使用这些属性而不是标准的平台属性:

ImageViewImageButton

  • 不要使用:android:src
  • 应该使用:app:srcCompat

CheckBoxRadioButton

  • 不要使用:android:button
  • 应该使用:app:buttonCompat

TextViewas of appcompat:1.1.0):

  • 不要使用:android:drawableStartandroid:drawableTop
  • 应该使用:app:drawableStartCompatapp:drawableTopCompat

由于这些属性是 appcompat 库的一部分,请确保使用 app: namespace。在内部,这些 AppCompat 视图使用 AppCompatResources 来支持加载矢量的加载。

如果你想了解 appcompat 如何交换出 TextView,或者声明了一个启用此功能的 AppCompatTextView 等,你可以查看这篇文章:helw.net/2018/08/06/…

实战

这些要求会影响你创建布局或访问资源所使用的方式。以下是一些考虑到的实际因素。

没有 compat 属性的视图

不幸的是,有很多地方你可能想要在不提供 compat 属性的视图上指定 drawables(例如,对于 progressbar 来说没有 indeterminateDrawableCompat 属性)。你仍然可以使用 AndroidX vectors,但你需要对代码作如下更改:

/* Copyright 2018 Google LLC.
   SPDX-License-Identifier: Apache-2.0 */
val progressBar = findViewById<ProgressBar>(R.id.loading)
val drawable = AppCompatResources.getDrawable(context, R.drawable.loading_indeterminate)
progressBar.indeterminateDrawable = drawable
复制代码

如果您正在使用数据绑定,那么可以使用自定义绑定适配器来完成此操作:

/* Copyright 2018 Google LLC.
   SPDX-License-Identifier: Apache-2.0 */
@BindingAdapter("indeterminateDrawableCompat")
fun bindIndeterminateProgress(progressBar: ProgressBar, @DrawableRes id: Int) {
  val drawable = AppCompatResources.getDrawable(progressBar.context, id)
  progressBar.indeterminateDrawable = drawable
}
复制代码

请注意,我们不希望数据绑定为我们加载 drawable(因为它目前不使用 AppCompatResources 来加载 drawables),所以不能像 @ {@ drawable / foo} 那样直接引用 drawable。相反,如果我们想将 drawable id 传递给绑定适配器,因此需要导入 R 来引用它:

<!-- Copyright 2018 Google LLC.
     SPDX-License-Identifier: Apache-2.0 -->
<layout ...>
  <data>
    <import type="your.package.R" alias="R" />
    ...
  </data>

  <ProgressBar ...
    app:indeterminateDrawableCompat="@{R.drawable.foo}" />

</layout>
复制代码

嵌套的 drawables

有些 drawable 是可嵌套的,例如 StateListDrawablesInsetDrawablesLayerDrawables 均包含其他子 drawable。AndroidX 支持显式渲染 <vector> 元素(也包括动画向量(animated-vector)和动画选择器(animated-selectors),但我们今天主要讨论静态 vectors)。当你调用 AppCompatResources.getDrawable,它用给定的 id 查看资源,如果它是一个向量(即根元素是 <vector>),它就会手动地为你加载它。否则,它就会把它交给系统加载——这样做的时候,AndroidX 就无法将自己重新插入到进程中。这意味着,如果你有一个包含向量的 InsetDrawable,并利用 AppCompatResources 加载它,它将根据 <inset> 标记,然后将它交给平台来加载。因此,它将没有机会加载嵌套的 <vector>,因此要么加载失败(在 API <21 上),要么返回到平台支持。

要解决这个问题,可以在代码中创建 drawables;也就是说,使用 AppCompatResources 加载矢量资源,然后手动创建 InsetDrawable 格式的 drawable。

有一个例外是 AndroidX 最近添加了一个新功能(从 appcompat:1.0.0 开始)—— AnimatedStateListDrawables 向后移植(译者注:原文是 back-ported ,Wikipedia 上解释是把新版本上的东西移植到老版本上去,这里翻译成向后移植)。这是 StateListDrawable 的一个版本,具有状态之间的动画转换(以 AnimatedVectorDrawables 的形式)。你不需要申明一个过渡。因此,如果你只需要一个可以使用 AndroidX 来扩充子向量的 StateListDrawable,那么你可以使用:

<!-- Copyright 2018 Google LLC.
     SPDX-License-Identifier: Apache-2.0 -->
<animated-selector ...>
  <item android:state_foo="true" android:drawable="@drawable/some_vector" />
  <item android:drawable="@drawable/some_other_vector" />
  <!-- no transitions specified -->
</animated-selector>
复制代码

一切都归功于这个天才黑客: twitter.com/alexjlockwo…

有一种方法可以在嵌套的 drawable 中启用矢量,通过使用 AppCompatDelegate#setCompatVectorFromResourcesEnabled,但它有许多缺点。务必仔细阅读 javadoc。

进程外加载

有时你需要在无法控制何时或如何加载的地方使用 drawable。例如:通知,主屏幕小部件或主题中指定的某些资源(例如,在创建预览窗口时设置由平台加载的 android:windowBackground)。在这些情况下,你不负责加载 drawable,因此没有机会集成 AndroidX 支持,你也就无法在 API 21 之前使用这些矢量资源了😞。

你当然可以在 API 21+ 上使用 vectors,但请注意,你可能不喜欢 AndroidX 提供的功能/错误修正。例如,虽然 AndroidX 对 fillType="evenOdd" 支持的很好,但是在 API 21-23 设备上不使用 AndroidX 支持向量是无法理解这个属性的。对于这个具体的例子,我将在下一篇文章中介绍如何在设计时转换 fillType。否则,你可能需要为不同的 API 准备不同的资源了:

res/
  drawable-xxhdpi/
    foo.png             <-- raster
  drawable-anydpi-v21/
    foo.xml             <-- vector
  drawable-anydpi-v24/
    foo.xml             <-- vector with fancy features
复制代码

请注意,除了 api 级别限定符之外,我们还需要在此处包含 anydpi 资源限定符。这是由于资源限定符优先级的工作方式导致的。任何在 drawable- <whatever> dpi 中的资源都被认为是比在 drawable-v21 更好的选择。

X 标记点

本文旨在强调使用 AndroidX 矢量支持(AndroidX vector support)的好处以及一些你需要注意的限制。使用 AndroidX 支持既可以在更多平台版本和后端功能上使用矢量资源,也可以让你接收任何未来的更新。

现在我们已经理解了为什么以及如何使用向量,下一篇文章将深入探讨如何创建它们。

即将推出:为 Android 创建矢量资源

即将推出:Android VectorDrawables 分析

如果发现译文存在错误或其他需要改进的地方,欢迎到 掘金翻译计划 对译文进行修改并 PR,也可获得相应奖励积分。文章开头的 本文永久链接 即为本文在 GitHub 上的 MarkDown 链接。


掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 AndroidiOS前端后端区块链产品设计人工智能等领域,想要查看更多优质译文请持续关注 掘金翻译计划官方微博知乎专栏

关注下面的标签,发现更多相似文章
评论