为什么矢量可绘制比例没有按预期缩放?


97

我正在尝试在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(在运行棒棒糖的大约2015年移动设备上较大的高分辨率)上显示图标时,产生此模糊图像:

模糊的BellIcon

将可绘制矢量的定义中的宽度和高度更改为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

@Cory Charlton。好奇。我将Studio 1.5.1与Gradle 1.5.0一起使用。将最低级别更改为21后,图像在APK中出现的唯一位置是drawable文件夹。矢量可绘制xml文件中的宽度/高度为24dp。指定高/宽为400dp的ImageView肯定会造成缩放比例差的图像。
克里斯·奈特

那就是我所期望的。我最终选择的唯一原因drawable-anydpi-v21是我的minSdkVersion年龄小于21。如果将设置minSdkVersion为小于21 ,行为会有任何变化吗?将XML迁移到drawable-anydpi哪里呢?我不希望有任何变化,但我也希望您的矢量图像能够正确缩放...
Cory Charlton

将最低版本更改回15将产生与您相同的结果。单个xml文件,drawable-anydpi-v21其中包含mdi / hdpi / etc中的各种光栅化图像。文件夹。不过,最终渲染结果没有变化。
克里斯·奈特

很奇怪...我使用您的图片修改了我的一个应用程序,并且效果很好(请参阅我的修改后的答案)
Cory Charlton 2016年

Answers:


100

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

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

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

来自Google工程师:

嗨,让我知道scaleType ='fitXY'是否可以解决您的问题,以使图像看起来更清晰。

棉花糖Vs棒棒糖是由于对棉花糖添加了特殊的洗牙处理。

另外,对于您的评论:“正确的行为:vector drawable应该缩放而不会有质量损失。因此,如果我们想在我们的应用程序中以3种不同的大小使用相同的资产,则不必重复将vector_drawable.xml重复3次,硬编码的大小。”

即使我完全同意这种情况,但实际上,Android平台存在性能问题,因此我们还没有达到理想的世界。因此,实际上,如果确定要同时在屏幕上绘制3个不同的大小,则建议使用3个不同的vector_drawable.xml以获得更好的性能。

技术细节基本上是我们在钩子下使用一个位图来缓存复杂的路径渲染,这样我们就可以获得与重绘可绘制位图相当的最佳重绘性能。


27
好的,谷歌,我们必须复制粘贴具有相同视口和路径且大小不同的相同可绘制对象,这绝对是可怕的。
Miha_x64

这将其固定在显示我的徽标像素化的设备上,但是在另一个之前一切正常的设备上,现在的纵横比是错误的。我不明白为什么这是一个问题,这是一个向量!不是像素!:P
tplive

@Harish Gyanani,android:scaleType =“ fitXY”解决了问题,但是动态图标而不是正方形图标又如何呢?
Manuela

28

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生成可绘制矢量的光栅图像,以实现向后兼容。您可以Drawable使用Java代码或@drawableXML代码来引用矢量资产。当您的应用运行时,相应的矢量或光栅图像会根据API级别自动显示。


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

6.x上的好铃声

在模拟器SDK版本21中:

5.x上的烂铃


1
感谢Cory,我看过该文档,也发现它不是很有用。我的设备是棒棒糖(v22),但无论是否在ImageView中指定了高度/重量,我都无法很好地缩放图形,使其超过矢量drawable中指定的24dp。我已经测试了具有目标和编译级别23和最小级别21的清单,但是如果不更改可绘制对象本身的宽度/高度,它将无法平滑地放大矢量可绘制对象。我真的不想创建多个可绘制对象,唯一的区别是高度/宽度!
克里斯·奈特

1
感谢您的确认,非常感谢您的帮助。如您所展示的,它显然应该可以按我期望的那样工作。使用您的确切代码将产生与最初相同的结果。令人沮丧!
克里斯·奈特

1
更新:将我的代码针对使用API​​ 22的仿真器运行,我仍然得到相同的结果。将我的代码针对具有API 23的仿真器运行,tada !,它可以正常工作。看起来升级仅适用于API 23+设备。尝试更改目标SDK版本,但这没有什么区别。
克里斯·奈特

您找到此问题的解决方案了吗?
dazza5000

@corycharlton是好的去除width height viewport heightwidthxml
VINNUSAURUS

9

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

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

首先,请确保您使用的是最新版本的AppCompat,并且已启用新的Vector实现:

android {
  defaultConfig {
    vectorDrawables.useSupportLibrary = true
  }
}

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

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

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(可能更早)给出了一个皮棉错误:“只能从同一库组调用”。我正在使用的构建工具仍然可以构建它,但是我不确定是否会产生运行时后果。即将测试。
androidguy

只能从同一库组调用,请添加以下内容来消除此错误:lintOptions {disable'RestrictedApi'}
Usama Saeed美国

6

1)为什么在矢量可绘制对象中有宽度/高度规范?我认为这些的全部目的是它们无损地进行缩放,从而使宽度和高度的定义毫无意义吗?

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

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

是的,只要正在运行的设备使用的是棒棒糖或更高版本,就可以进行调整大小,并且我没有任何问题。在以前的API中,构建应用时,矢量会转换为不同屏幕尺寸的png。

3)如何有效使用width / height属性,以及viewportWidth / Height有何区别?

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


谢谢Emiura。我会对您如何使用同一个可绘制对象完成多个渲染尺寸感兴趣。使用24dp可绘制对象(出于测试目的),我已将ImageView更改为指定的宽度和高度为400dp,并在我的Lollipop设备(v22)上运行,但它仍然表现不佳,就像光栅化的放大图像而不是矢量图像。还更改了我的清单,使其具有针对v23和最低版本21的目标并进行编译。没有区别。
克里斯·奈特

在这种情况下,您只需要在矢量可绘制对象中使用最大尺寸,然后在文本视图中指定24dp尺寸即可。
emirua '16

我现在看到,当在矢量可绘制区域中的大小与Lollipop中布局中的大小之间存在非常大的差异时,您会看到模糊的图像。但是,仅设置最大大小而不是为不同屏幕大小提供一堆文件还是要整洁得多;)。
emirua '16

4

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

<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 '19

3

我尝试了这些方式,但是不幸的是,它们都没有起作用。我尝试fitXYImageView,我尝试使用app:srcCompat,但甚至不能使用它。但是我找到了一种技巧:

将Drawable设置backgroundImageView

但是这种方法的重点是视图维度。如果ImageView尺寸不正确,drawable会拉伸。您必须以棒棒糖之前的版本或“使用某些视图”控制它PercentRelativeLayout

希望对您有所帮助。


2

首先:将svg导入drawble:((https://www.learn2crack.com/2016/02/android-studio-svg.html))

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

在此处输入图片说明

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

在此处输入图片说明

第二:如果要从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方法上添加以下行

 @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填充类型应用于矢量)时,下次构建应用程序时一切正常。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.