为什么我的矢量图形不按预期缩放?

114

我正尝试在我的Android应用中使用矢量图。从http://developer.android.com/training/material/drawables.html(重点在于我):

在Android 5.0(API Level 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"/>

在尝试在一部2015年左右的高分辨率移动设备(运行Lollipop)上以400dp显示图标时,会产生这种模糊的图像:

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 Studio吗? Android Studio的哪个版本和Gradle插件的哪个版本?当我在Android Studio中右键单击drawable文件夹并选择“New -> Vector Asset”时,它会将矢量图像XML放入我的drawable文件夹中。但是,如果我使用apktool来解包构建的APK,我会发现XML文件位于“drawable-anydpi-v21”中,并且在API 21+设备上正确缩放。光栅文件放置在“drawable-<mdpi/hdpi/etc>-v4”文件夹中,并且不在API 21+设备上使用(基于它们正确缩放的事实)。 - Cory Charlton
这正是我所期望的。我最终只会得到 drawable-anydpi-v21 的原因是我的 minSdkVersion 小于 21。如果将 minSdkVersion 设置为小于 21,是否会有任何行为变化?如果将 XML 移动到 drawable-anydpi 中呢?我不认为会有任何变化,但我也希望您的矢量图像能够正确缩放... - Cory Charlton
将最小版本改回15会产生与您相同的结果。drawable-anydpi-v21中有一个单独的xml文件,其中包含不同分辨率的mdi/hdpi等光栅化图像文件夹。但最终呈现的结果没有变化。 - Chris Knight
非常奇怪...我使用了你的图片修改了我的一个应用程序,它运行良好(请参见我的编辑答案)。 - Cory Charlton
很高兴看到这个问题(和讨论)。我也遇到了同样的情况。所以看起来在v21中不支持矢量图形的缩放,只有v23+ :) - 34m0
显示剩余3条评论
10个回答

117

关于此问题,这里有最新的信息:

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

看起来在 Lollipop 上使用 android:scaleType="fitXY" 可以使其正确缩放。

来自 Google 工程师的说法:

你好,如果你认为 scaleType='fitXY' 可以解决问题,可以试试看。

Marshmallow 和 Lollipop 的区别是 Marshmallow 加入了一种特殊的缩放处理方式。

另外,针对你的评论:"正确的行为:矢量图应该无损缩放。因此,如果我们想在应用程序中使用相同的资产的 3 种不同的大小,我们不必将 vector_drawable.xml 三次复制并硬编码为不同的大小。" 虽然我完全同意这个观点,但实际上,Android 平台存在性能问题,因此,如果你确定要在屏幕上同时绘制 3 种不同的大小,建议使用 3 个不同的 vector_drawable.xml 以获得更好的性能。

技术上讲,我们在后台使用位图来缓存复杂的路径渲染,以便获得与重新绘制位图 drawable 相当的最佳重绘性能。


37
好的,谷歌,我们必须复制粘贴完全相同的可绘制图形,它们具有相同的视口和路径,只是大小不同,这实在是太糟糕了。 - Miha_x64
这解决了设备上我的标志出现像素化的问题,但在另一个单位上,在一切正常之前,它现在是错误的纵横比。我不明白为什么这是个问题,这是矢量图啊!不是像素! :P - tplive
2
@Harish Gyanani,android:scaleType="fitXY" 可以解决问题,但是对于动态和非正方形的图标怎么办? - Manuela
android:scaleType="fitCenter" 对我很有效,而不是使用 android:scaleType="fitXY"。 - Tajveer Singh Nijjar

31

1) 为什么矢量图形中需要指定宽度/高度?我认为这些的整个意义在于它们可以无损缩放,使得宽度和高度在定义中没有意义。

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

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

如果您还需要针对低于21的SDK进行目标定位,则我认为这是不可能的。

3) 如何有效地使用宽度/高度属性,viewportWidth/Height有什么区别?

文档:(实际上现在重新阅读后并不是很有用...)

android:width

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

android:height

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

android:viewportWidth

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

android:viewportHeight

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

更多文档:

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

编辑:出现了一些奇怪的问题。以下是我在模拟器SDK版本23(Lollipop+测试设备现在已经死了...)中得到的结果:

Good bells on 6.x

在模拟器SDK版本21中:

Crappy bells on 5.x


1
谢谢Cory,我看过那个文档但是发现它并没有太大用处。我的设备是Lollipop(v22),但无论ImageView中是否精确指定高度/宽度,都不能很好地缩放图形以超过矢量可绘制中指定的24dp。我已经测试了一个目标和编译级别为23,最小级别为21的清单,但是它无法平稳地升级我的矢量可绘制对象,除非我在可绘制对象本身中更改宽度/高度。我真的不想创建仅在高度/宽度上有所区别的多个可绘制对象! - Chris Knight
1
感谢您的确认,非常感谢您的帮助。正如您所展示的那样,它应该按照我的期望工作。使用您的精确代码产生了与我最初的结果相同的结果。令人沮丧! - Chris Knight
1
更新:在 API 22 模拟器上运行我的代码,结果仍然相同。在 API 23 模拟器上运行我的代码,瞧!它有效了。看起来只有在 API 23+ 设备上才能进行升级。尝试更改目标 SDK 版本,但没有任何影响。 - Chris Knight
你找到这个问题的解决方案了吗? - dazza5000
@corycharlton,从xml中删除widthheightviewportheightwidth是否可以? - VINNUSAURUS

13

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();
        }
    }
}

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

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

您的矢量可绘制图形现在应该可以正确缩放了!


这是一个不错的方法,但是AppCompat 25.4(以及之前的版本)会给出一个lint错误:“只能从同一库组中调用”。我使用的构建工具仍然可以构建它,但我不确定是否会有运行时后果。我即将进行测试。 - androidguy
只能从同一库组中调用,通过添加以下内容来消除此错误: lintOptions { disable 'RestrictedApi' } - Usama Saeed US

9
  1. 矢量图中为什么要设置宽度/高度?我认为这些的整个意义在于它们可以无损缩放,使得宽度和高度在定义中毫无意义。

这只是矢量图的默认大小,以防您没有在布局视图中定义它。(例如,您对imageview的高度和宽度使用wrap content)

  1. 是否可以使用单个矢量图作为24dp drawable在TextView上以及大型接近屏幕宽度的图像上使用?

是的,只要运行设备使用的是lollipop或更高版本,调整大小时我没有遇到任何问题。在以前的API中,构建应用程序时,矢量图会转换为不同屏幕尺寸的png。

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

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


谢谢Emiura。我对你如何使用相同的可绘制对象实现多个渲染尺寸很感兴趣。我已经将我的ImageView更改为指定宽度和高度为400dp,并在我的Lollipop设备(v22)上运行,但它仍然呈现得很差,就像一个光栅化的放大图像而不是矢量图像。我还将我的清单更改为针对v23和最小版本21进行编译。没有任何区别。 - Chris Knight
在这种情况下,您只需要在矢量可绘制对象中使用最大尺寸,然后在文本视图中指定24dp的大小。 - emirua
我现在明白了,当你在Lollipop中将矢量可绘制对象的大小与布局中的大小存在非常大的差异时,放大后会出现模糊图像。然而,仍然比为不同屏幕尺寸创建一堆文件更整洁,只需设置最大尺寸即可。;) - emirua
当您在矢量可绘制对象的大小与布局中的大小之间存在非常大的差异时,放大图像会导致模糊。我们所说的“非常大”是指多大? - alpharoz

5

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

<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">

它对我有用。


谢谢,这已足以解决我平板电脑上出现的像素化问题。 - user2342558

4
我尝试了这些方法,但不幸的是,它们中的任何一种都没有起作用。 我尝试在ImageView中使用fitXY,我尝试使用app:srcCompat,但甚至无法使用它。 但我找到了一个诀窍:
将Drawable设置为您的ImageViewbackground
但这种方法的关键是视图的维度。如果您的ImageView的尺寸不正确,则可拉伸可绘制物。您必须在早期版本中进行控制,或者使用一些视图PercentRelativeLayout
希望对您有所帮助。

2

首先: 将SVG导入到drawable文件夹中:((https://www.learn2crack.com/2016/02/android-studio-svg.html))

1- 右键单击drawable文件夹,选择New -> Vector Asset

enter image description here

2- 矢量素材工作室提供了选择内置Material图标或本地SVG文件的选项。如果需要,您可以覆盖默认大小。

enter image description here

其次: 如果要支持Android API 7+,则必须使用Android Support Library,如下所示:((https://dev59.com/9F4b5IYBdhLWcg3wqDSj#44713221))

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方法中添加以下行:

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

你的解决方案在API 21及以上版本上是否可用?user3210008提到该实现不适用于API 21及以上版本。 - AJW

2
对我而言,导致向量图被转换成PNG格式的原因是我的矢量图中使用了android:fillType="evenodd"属性。当我在我的可绘制矢量图中删除了该属性的所有出现(因此向量应用了默认的nonzero填充类型)后,下次构建应用程序时一切都正常。"最初的回答"

0

通过添加解决

defaultConfig {
    vectorDrawables.useSupportLibrary = true
}

要建立 build.gradle (module:app) 文件,并替换 imageViews

android:src="..."

使用

app:srcCompat="..."

希望能够帮到你


0

除了置顶答案外,根据我的经验,使用ConstraintLayout而不是LinearLayout或FrameLayout可以消除SVG矢量图形的模糊。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接