在 Android 应用中使用矢量资源 [复制链接]

2019-8-8 10:20
谷歌开发者 阅读:587 评论:0 赞:0
Tag:  矢量

在这篇文章中,我们将会深入研究如何在你的 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

TextView(as 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 之前使用这些矢量资源了


我来说两句
您需要登录后才可以评论 登录 | 立即注册
facelist
所有评论(0)
领先的中文移动开发者社区
18620764416
7*24全天服务
意见反馈:1294855032@qq.com

扫一扫关注我们

Powered by Discuz! X3.2© 2001-2019 Comsenz Inc.( 粤ICP备15117877号 )