为什么我的矢量可绘制对象缩放不符合预期?

Why isn't my vector drawable scaling as expected?

提问人:Chris Knight 提问时间:1/22/2016 最后编辑:Chris Knight 更新时间:7/7/2022 访问量:107848

问:

我正在尝试在我的 Android 应用程序中使用矢量可绘制对象。从 http://developer.android.com/training/material/drawables.html(强调我的):

在 Android 5.0(API 级别 21)及更高版本中,您可以定义矢量可绘制对象,这些可绘制对象在不丢失定义的情况下进行缩放。

使用此可绘制对象:

<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="24dp"
android:width="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path android:fillColor="@color/colorPrimary" android:pathData="M14,20A2,2 0 0,1 12,22A2,2 0 0,1 10,20H14M12,2A1,1 0 0,1 13,3V4.08C15.84,4.56 18,7.03 18,10V16L21,19H3L6,16V10C6,7.03 8.16,4.56 11,4.08V3A1,1 0 0,1 12,2Z" />

和这个 ImageView:

<ImageView
    android:layout_width="400dp"
    android:layout_height="400dp"
    android:src="@drawable/icon_bell"/>

当尝试以 400dp 显示图标时(在运行 Lollipop 的大约 2015 年的大型高分辨率移动设备上)时,会产生以下模糊图像:

blurryBellIcon

将矢量可绘制对象定义中的宽度和高度更改为 200dp 可显著改善 400dp 渲染大小的情况。但是,将其设置为 TextView 元素(即文本左侧的图标)的可绘制对象现在会创建一个巨大的图标。

我的问题:

1) 为什么矢量可绘制对象中有宽度/高度规格?我认为这些的全部意义在于它们无损地向上和向下缩放,使宽度和高度在其定义中毫无意义?

2) 是否可以使用单个矢量可绘制对象,该可绘制对象在 TextView 上用作 24dp 可绘制对象,但也可以很好地放大以使用更大的图像?例如,如何避免创建多个不同大小的矢量可绘制对象,而是使用一个可根据我的渲染要求缩放的可绘制对象?

3) 如何有效地使用 width/height 属性,与 viewportWidth/Height 有什么区别?

其他详细信息:

  • 设备正在运行 API 22
  • 将 Android Studio v1.5.1 与 Gradle 版本 1.5.0 结合使用
  • 清单为编译,目标级别为 23,最低级别为 15。我也尝试将最低级别移动到 21,但这没有区别。
  • 反编译 APK(最小级别设置为 21)会在 drawable 文件夹中显示单个 XML 资源。不会生成栅格化图像。
Android 矢量 可绘制缩

评论

0赞 Cory Charlton 1/23/2016
只是为了清楚。您使用的是 Android Studio?什么版本的 Android Studio 和什么版本的 Gradle 插件?当我右键单击 Android Studio 中的可绘制对象文件夹并选择它时,它会将矢量图像 XML 放在我的可绘制对象文件夹中。但是,如果我使用 apktool 解压缩构建的 APK,我会看到 XML 文件在 API 21+ 设备上并正确缩放。栅格文件放置在文件夹中,不在 API 21+ 设备上使用(基于它们正确缩放的事实)New -> Vector Assetdrawable-anydpi-v21drawable-<mdpi/hdpi/etc>-v4
0赞 Chris Knight 1/23/2016
@Cory查尔顿。好奇。我正在使用 Studio 1.5.1 和 Gradle 1.5.0。将 min level 更改为 21 后,图像在 APK 中显示的唯一位置是文件夹。矢量可绘制 xml 文件中的宽度/高度为 24dp。指定 400dp 高度/宽度的 ImageView 肯定会创建缩放不佳的图像。drawable
0赞 Cory Charlton 1/23/2016
这就是我所期望的。我最终得到的唯一原因是因为我的年龄小于 21 岁。如果将 设置为小于 21,行为会有什么变化吗?将 XML 移动到 怎么样?我不希望有变化,但我也希望您的矢量图像能够正确缩放......drawable-anydpi-v21minSdkVersionminSdkVersiondrawable-anydpi
0赞 Chris Knight 1/23/2016
将最小版本改回 15 会产生与您相同的结果。一个 xml 文件,其中包含 mdi/hdpi/etc. 文件夹中的各种光栅化图像。不过,最终渲染结果没有变化。drawable-anydpi-v21
0赞 Cory Charlton 1/23/2016
很奇怪......我使用您的图像修改了我的一个应用程序,它运行良好(请参阅我编辑的答案)

答:

31赞 Cory Charlton 1/22/2016 #1

1) 为什么矢量可绘制对象中有宽度/高度规格?我认为这些的全部意义在于它们无损地向上和向下缩放,使宽度和高度在其定义中毫无意义?

对于小于 21 的 SDK 版本,其中构建系统需要生成光栅图像,并且在未指定宽度/高度的情况下作为默认大小。

2) 是否可以使用单个矢量可绘制对象,该可绘制对象在 TextView 上用作 24dp 可绘制对象以及大型近屏宽度图像?

如果您还需要针对小于 21 的 SDK,我认为这是不可能的。

3) 如何有效地使用 width/height 属性,与 viewportWidth/Height 有什么区别?

文档:(实际上现在我重新阅读它不是很有用......

android:width

用于定义可绘制对象的固有宽度。这支持所有尺寸单位,通常用 dp 指定。

android:height

用于定义可绘制对象的固有高度。这支持所有尺寸单位,通常用 dp 指定。

android:viewportWidth

用于定义视口空间的宽度。视口基本上是绘制路径的虚拟画布。

android:viewportHeight

用于定义视口空间的高度。视口基本上是绘制路径的虚拟画布。

更多文档:

Android 4.4(API 级别 20)及更低版本不支持矢量可绘制对象。如果您的最低 API 级别设置为这些 API 级别之一,Vector Asset Studio 还会指示 Gradle 生成矢量可绘制对象的光栅图像,以实现向后兼容性。您可以像在 Java 代码或 XML 代码中引用矢量资产一样;当您的应用运行时,相应的矢量或光栅图像会自动显示,具体取决于 API 级别。Drawable@drawable


编辑:奇怪的事情正在发生。这是我在模拟器 SDK 版本 23 中的结果(Lollipop+ 测试设备现在已死......

Good bells on 6.x

在模拟器 SDK 版本 21 中:

Crappy bells on 5.x

评论

1赞 Chris Knight 1/22/2016
谢谢 Cory,我看过那个文档,也发现它不是很有用。我的设备是 Lollipop (v22),但我无法让图形很好地缩放,高于矢量可绘制对象中指定的 24dp,无论高度/重量是否在 ImageView 中完全指定。我已经测试了一个具有目标和编译级别 23 和最小级别 21 的清单,但如果不更改可绘制对象本身的宽度/高度,它将无法顺利地升级我的矢量可绘制对象。我真的不想创建多个可绘制对象,其唯一区别是高度/宽度!
1赞 Chris Knight 1/23/2016
感谢您的确认,非常感谢您的帮助。正如你所展示的,它显然应该像我所期望的那样工作。使用您的确切代码会产生与我最初相同的结果。令人 沮丧!
1赞 Chris Knight 1/23/2016
更新:使用 API 22 针对模拟器运行我的代码,我仍然得到相同的结果。使用 API 23 针对模拟器运行我的代码,tada!,它可以工作。看起来升级仅适用于 API 23+ 设备。尝试更改目标 SDK 版本,但这没有区别。
0赞 dazza5000 3/9/2019
你找到这个问题的解决方案了吗?
0赞 VINNUSAURUS 4/5/2019
@corycharlton可以删除和删除吗?widthheightviewportheightwidthxml
9赞 emirua 1/22/2016 #2
  1. 为什么矢量可绘制对象中有宽度/高度规范?我认为这些的全部意义在于它们无损地向上和向下缩放,使宽度和高度在其定义中毫无意义?

这只是矢量的默认大小,以防您未在布局视图中定义它。(即,您将包装内容用于高度和 imageview 的宽度)

  1. 是否可以使用单个矢量可绘制对象,该可绘制对象在 TextView 上用作 24dp 可绘制对象以及大型近屏宽度图像?

是的,这是可能的,我在调整大小方面没有任何问题 只要正在运行的设备使用棒棒糖或更高。在上一页 API,当矢量转换为不同屏幕尺寸的 png 时 您构建应用。

  1. 如何有效地使用 width/height 属性,与 viewportWidth/Height 有什么区别?

这会影响矢量周围空间的使用方式。即您可以 用它来改变视口内矢量的“重力”,使 向量具有默认边距,保留向量的某些部分 在视口之外,等等...通常,您只需将它们设置为相同 大小。

评论

0赞 Chris Knight 1/22/2016
谢谢惠浦。我很想知道您如何使用相同的可绘制对象完成多个渲染大小。使用 24dp 可绘制对象(用于测试目的),我已将 ImageView 更改为具有 400dp 的指定宽度和高度,并在我的 Lollipop 设备 (v22) 上运行,但它的渲染效果仍然很差,就像光栅放大图像而不是矢量图像。还更改了我的清单,使其针对 v23 和 min 版本 21 进行目标和编译。没有区别。
0赞 emirua 1/23/2016
在这种情况下,您只需要在矢量可绘制对象中使用最大尺寸,然后在文本视图中指定 24dp 大小。
0赞 emirua 1/23/2016
我现在看到,当您放大时,您会得到模糊的图像,矢量可绘制对象的大小与Lollipop布局中的大小之间存在非常大的差异。但是,只需设置最大尺寸,而不是;)为不同的屏幕尺寸设置一堆文件,它仍然更加简洁。
0赞 alpharoz 6/23/2021
"当你放大时,你会得到模糊的图像,矢量可绘制对象的大小和布局中的大小之间的差异非常大“我们在说什么?
13赞 user3210008 3/6/2016 #3

AppCompat 23.2 为所有运行 Android 2.1 及更高版本的设备引入了矢量可绘制对象。无论矢量可绘制对象的 XML 文件中指定的宽度/高度如何,图像都可以正确缩放。遗憾的是,此实现未用于 API 级别 21 及更高版本(支持本机实现)。

好消息是,我们可以强制 AppCompat 在 API 级别 21 和 22 上使用其实现。 坏消息是,我们必须使用反射来做到这一点。

首先,确保您使用的是最新版本的 AppCompat,并且您已经启用了新的向量实现:

android {
  defaultConfig {
    vectorDrawables.useSupportLibrary = true
  }
}

dependencies {
    compile 'com.android.support:appcompat-v7:23.2.0'
}

然后,调用应用程序的 onCreate():useCompatVectorIfNeeded();

private void useCompatVectorIfNeeded() {
    int sdkInt = Build.VERSION.SDK_INT;
    if (sdkInt == 21 || sdkInt == 22) { //vector drawables scale correctly in API level 23
        try {
            AppCompatDrawableManager drawableManager = AppCompatDrawableManager.get();
            Class<?> inflateDelegateClass = Class.forName("android.support.v7.widget.AppCompatDrawableManager$InflateDelegate");
            Class<?> vdcInflateDelegateClass = Class.forName("android.support.v7.widget.AppCompatDrawableManager$VdcInflateDelegate");

            Constructor<?> constructor = vdcInflateDelegateClass.getDeclaredConstructor();
            constructor.setAccessible(true);
            Object vdcInflateDelegate = constructor.newInstance();

            Class<?> args[] = {String.class, inflateDelegateClass};
            Method addDelegate = AppCompatDrawableManager.class.getDeclaredMethod("addDelegate", args);
            addDelegate.setAccessible(true);
            addDelegate.invoke(drawableManager, "vector", vdcInflateDelegate);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }
}

最后,确保您使用的是 ImageView 的图像,而不是设置图像:app:srcCompatandroid:src

<ImageView
    android:layout_width="400dp"
    android:layout_height="400dp"
    app:srcCompat="@drawable/icon_bell"/>

您的矢量可绘制对象现在应该可以正确缩放!

评论

0赞 androidguy 11/8/2017
好方法,但是,AppCompat 25.4(也许更早)给出了一个 lint 错误“只能从同一个库组调用”。我使用的构建工具仍然允许它构建,但我不确定是否会有运行时后果。即将测试。
0赞 Usama Saeed US 7/16/2019
只能从同一个库组调用,通过添加以下内容来删除此错误: lintOptions { disable 'RestrictedApi' }
117赞 Taehyun Park 3/11/2016 #4

这里有关于这个问题的新信息:

https://code.google.com/p/android/issues/detail?id=202019

看起来使用将使它在 Lollipop 上正确缩放。android:scaleType="fitXY"

来自 Google 工程师:

嗨,让我知道 scaleType='fitXY' 是否可以成为您的解决方法,在 为了让图像看起来清晰。

棉花糖与棒棒糖是由于特殊的缩放处理 加入棉花糖。

另外,供您评论:“ 正确行为:矢量可绘制对象 应该在不损失质量的情况下进行扩展。因此,如果我们想使用相同的资产 在我们的应用程序中有 3 种不同的尺寸,我们不必复制 vector_drawable.xml 3 次,具有不同的硬编码大小。"

尽管我完全同意情况应该是这样,但实际上, Android平台存在性能问题,因此我们无法达到 理想的世界。所以实际上建议使用 3 种不同的 如果您确定需要,vector_drawable.xml以获得更好的性能 同时在屏幕上绘制 3 个不同的尺寸。

技术细节基本上是我们在钩子下使用位图 缓存复杂的路径渲染,这样我们才能得到最好的 重绘性能,与重绘位图可绘制对象相当。

评论

37赞 Miha_x64 4/20/2017
好吧,谷歌,我们必须复制粘贴具有相同视口和路径的相同可绘制对象,但大小不同,这绝对是可怕的。
0赞 tplive 11/18/2017
这修复了它在显示我的徽标像素化的设备上,但在另一个设备上,以前一切都很好,现在它是错误的纵横比。我不明白为什么这是一个问题,这是一个向量!不是像素!:P
2赞 Manuela 1/30/2019
@Harish Gyanani,android:scaleType=“fitXY”解决了这个问题,但是动态图标而不是平方图标呢?
0赞 Tajveer Singh Nijjar 3/10/2021
android:scaleType=“fitCenter”对我有用,而不是android:scaleType=“fitXY”。
4赞 Mahdi Astanei 3/15/2016 #5

我必须尝试这些方法,但不幸的是,它们都没有奏效。 我尝试使用,我尝试使用,但甚至不能使用它。 但我找到了一个技巧方法:fitXYImageViewapp:srcCompat

将 Drawable 设置为 .backgroundImageView

但这种方式的重点是视图维度。如果尺寸不正确,则可绘制对象将获得拉伸。您必须在 pre-lollipop 版本或 Use some Views 中控制它。ImageViewPercentRelativeLayout

希望对您有所帮助。

2赞 Milad Ahmadi 10/10/2017 #6

第一个:将 svg 导入到绘图:((https://www.learn2crack.com/2016/02/android-studio-svg.html))

1-右键单击可绘制文件夹,选择新建 -> 矢量资产

enter image description here

2- Vector Asset Studio 为您提供了选择内置的选项 材质图标或您自己的本地 svg 文件。您可以覆盖 如果需要,默认大小。

enter image description here

第二:如果你想得到android API 7+的支持,你必须使用android支持库((https://stackoverflow.com/a/44713221/1140304))

1-添加

vectorDrawables.useSupportLibrary = true

添加到您的 build.gradle 文件中。

2-在布局中使用具有 imageView 的命名空间:

xmlns:app="http://schemas.android.com/apk/res-auto"

第三:使用 android:scaleType=“fitXY” 设置 imageView 并使用 app:srcCompat

 <ImageView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scaleType="fitXY"
        app:srcCompat="@drawable/ic_menu" />

第四:如果你想在java代码中设置svg,你必须在oncreate methoed上添加以下行

 @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        AppCompatDelegate.setCompatVectorFromResourcesEnabled(true);
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

评论

0赞 AJW 8/9/2018
您的解决方案是否适用于 API 21 及更高版本?上面的 user3210008 提到该实现未用于 API 21 及更高版本。
5赞 Djek-Grif 8/13/2018 #7

我解决了我的问题,只是更改矢量图像的大小。从一开始,它就是这样的资源:

<vector xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:aapt="http://schemas.android.com/aapt"
    android:width="24dp"
    android:height="24dp"
    android:viewportHeight="300"
    android:viewportWidth="300">

我已将其更改为 150dp(使用此矢量资源布局中 ImageView 的大小),如下所示:

<vector xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:aapt="http://schemas.android.com/aapt"
    android:width="150dp"
    android:height="150dp"
    android:viewportHeight="300"
    android:viewportWidth="300">

它对我有用。

评论

0赞 user2342558 12/28/2019
谢谢,这足以解决我的平板电脑上出现像素化的问题。
2赞 Mahozad 7/28/2019 #8

对我来说,导致矢量转换为PNG的原因是我的矢量可绘制对象中的android:fillType=“evenodd”属性。当我删除了可绘制对象中所有出现的此属性时(因此应用于矢量的默认填充类型),下次构建应用程序时,一切都很好。nonzero

0赞 Bitwise DEVS 8/19/2021 #9

除了最重要的答案之外,在我这边,移动到 ConstraintLayout 而不是 LinearLayout 或 FrameLayout 可以消除 SVG 矢量可绘制对象上的模糊。

0赞 ahzare 10/20/2021 #10

通过添加解决

defaultConfig {
    vectorDrawables.useSupportLibrary = true
}

到 buil.gradle (module:app) 文件,并将 imageViews 替换为

android:src="..."

app:srcCompat="..."

希望帮助