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

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

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

首先是 AndroidX

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

这会显著增加其使用?#25945;?#30340;范围,不仅如此,它还支持将特性和 bug 修复程序向后移植到旧?#25945;ā?#20363;如,使用 AndroidX 中的 VectorDrawableCompat 可以:

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

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

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

Alex Lockwood 是这?#27492;?#30340;

怎么使用?

为了使用 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/,因为系?#25345;?#36947;这就是 VectorDrawable 类被引入的时候。

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

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

2. 使用 AndroidX 加载

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

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

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

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 如何?#25442;?#20986; TextView,或者声明了一个启用此功能的 AppCompatTextView 等,你可以查看这篇文章:helw.net/2018/08/06/…

实战

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

没有 compat 属性的视图

不幸的是,有很多地方你可能想要在不提供 compat 属性的视图?#29616;?#23450; drawables(例如,对于 progressbar ?#27492;?#27809;有 indeterminateDrawableCompat 属性)。你仍然可以使用 AndroidX vectors,但你需要?#28304;?#30721;作如下更改:

/* 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> 标记,然后将它交给?#25945;?#26469;加载。因此,它将没有机会加载嵌套的 <vector>,因此要么加载失败(在 API <21 上),要么返回到?#25945;?#25903;持。

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

有一个例外是 AndroidX 最近添加了一个新功能(从 appcompat:1.0.0 开始)—— AnimatedStateListDrawables 向后移植(译者注:原文是 back-ported ,Wikipedia 上解释是?#30740;?#29256;本上的东西移植到老版本?#20808;?/code>,这里翻译成向后移植)。这是 StateListDrawable 的一个版本,具有状态之间的动画转换(以 AnimatedVectorDrawables 的?#38382;?。你不需要申明一个过渡。因此,如果你只需要一个可以使用 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。

进程外加载

有时你需要在无法控制何?#34987;?#22914;何加载的地方使用 drawable。例如?#21644;?#30693;,主屏幕小部件或主题中指定的某些资源(例如,在创建预?#26469;?#21475;时设置由?#25945;?#21152;载的 android:windowBackground)。在这些情况下,你不负责加载 drawable,因此没有机会集成 AndroidX 支持,你也就无法在 API 21 之前使用这些矢量资源了


我?#27492;?#20004;句
您需要登录后才可以评论 登录 | 立即注册
facelist
所有评论(0)
领先的中文移动开发者社区
18620764416
7*24全天服务
意见反馈:[email protected]

扫一扫关注我们

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

招财童子彩金