为什么向量的可绘制缩放不如预期?

我试图在我的 Android 应用程序中使用矢量绘图:

在 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
android:layout_width="400dp"
android:layout_height="400dp"
android:src="@drawable/icon_bell"/>

当试图以400dp 显示图标时(在一个大的高分辨率的大约2015年的移动设备上运行棒棒糖)产生这个模糊的图像:

blurryBellIcon

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

我的问题是:

1)为什么在矢量图中有一个宽度/高度规范?我认为这些东西的全部意义在于它们可以无损地向上和向下放大,使得宽度和高度在它的定义中变得毫无意义?

2)是否有可能使用一个矢量绘制,作为一个24dp 的工程上的 TextView 绘制,但扩展很好,以使用尽可能多的大图像?例如,我如何避免创建不同大小的多个矢量绘图,而是使用其中一个规模,以我呈现的需求?

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

其他详情:

  • 设备正在运行 API 22
  • 使用 Android Studio v1.5.1和 Gradle v1.5.0
  • 清单是编译和目标级别23,最低级别15。我也试过将最小等级移动到21,但是这没有什么区别。
  • 反编译 APK (最小级别设置为21)显示可绘制文件夹中的单个 XML 资源。不产生栅格化图像。
102195 次浏览

1) Why is there a width/height specification in the vector drawable? I thought the entire point of these is that they scale up and down losslessly making width and height meaningless in its definition?

For SDK versions less than 21 where the build system needs to generate raster images and as the default size in cases where you don't specify the width/height.

2) Is it possible to use a single vector drawable which works as a 24dp drawable on a TextView as well as a large near-screen width image?

I don't believe this is possible if you also need to target SDKs less than 21.

3) How do I effectively use the width/height attributes and what is the difference with viewportWidth/Height?

Documentation: (actually not very useful now that I re-read it...)

android:width

Used to define the intrinsic width of the drawable. This support all the dimension units, normally specified with dp.

android:height

Used to define the intrinsic height the drawable. This support all the dimension units, normally specified with dp.

android:viewportWidth

Used to define the width of the viewport space. Viewport is basically the virtual canvas where the paths are drawn on.

android:viewportHeight

Used to define the height of the viewport space. Viewport is basically the virtual canvas where the paths are drawn on.

More documentation:

Android 4.4 (API level 20) and lower doesn't support vector drawables. If your minimum API level is set at one of these API levels, Vector Asset Studio also directs Gradle to generate raster images of the vector drawable for backward-compatibility. You can refer to vector assets as Drawable in Java code or @drawable in XML code; when your app runs, the corresponding vector or raster image displays automatically depending on the API level.


Edit: Something weird is going on. Here's my results in the emulator SDK version 23 (Lollipop+ test device is dead right now...):

Good bells on 6.x

And in the emulator SDK version 21:

Crappy bells on 5.x

  1. Why is there a width/height specification in the vector drawable? I thought the entire point of these is that they scale up and down losslessly making width and height meaningless in its definition?

This is just the default size of the vector in case you don't define it in the layout view. (i.e. You use wrap content for the height and width of your imageview)

  1. Is it possible to use a single vector drawable which works as a 24dp drawable on a TextView as well as a large near-screen width image?

Yes, It is possible and I haven't had any problem with resizing as long as the running device is using lollipop or higher. In previous APIs, the vector is converted to pngs for different screen sizes when you build the app.

  1. How do I effectively use the width/height attributes and what is the difference with viewportWidth/Height?

This affects how the space around your vector is used. i.e. You can use it to change the "gravity" of the vector inside the viewport, make the vector have a default margin, leave certain parts of the vector out of the viewport, etc... Normally, you just set them to the same size.

AppCompat 23.2 introduces vector drawables for all devices running Android 2.1 and above. The images scale correctly, irregardless of the width/height specified in the vector drawable's XML file. Unfortunately, this implementation is not used in API level 21 and above (in favor of the native implementation).

The good news is that we can force AppCompat to use its implementation on API levels 21 and 22. The bad news is that we have to use reflection to do this.

First of all, make sure you're using the latest version of AppCompat, and that you have enabled the new vector implementation:

android {
defaultConfig {
vectorDrawables.useSupportLibrary = true
}
}


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

Then, call useCompatVectorIfNeeded(); in your Application's 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();
}
}
}

Finally, make sure you're using app:srcCompat instead of android:src to set your ImageView's image:

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

Your vector drawables should now scale correctly!

There is new info about this issue here:

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

It looks like using android:scaleType="fitXY" will make it scale correctly on Lollipop.

From a Google engineer:

Hi, Let me know if scaleType='fitXY' can be a workaround for you , in order to get the image look sharp.

The marshmallow Vs Lollipop is due to a special scaling treatment added into marshmallow.

Also, for your comments: " Correct behavior: The vector drawable should scale without quality loss. So if we want to use the same asset in 3 different sizes in our application, we don't have to duplicate vector_drawable.xml 3 times with different hardcoded sizes. "

Even though I totally agree this should be the case, in reality, the Android platform has performance concern such that we have not reach the ideal world yet. So it is actually recommended to use 3 different vector_drawable.xml for better performance if you are sure you want to draw 3 different size on the screen at the same time.

The technical detail is basically we are using a bitmap under the hook to cache the complex path rendering, such that we can get the best redrawing performance, on a par with redrawing a bitmap drawable.

I try must of these ways but unfortunately, none of them worked. I try fitXY in ImageView, I try using app:srcCompat but cannot even use it. but I found a trick way:

set the Drawable to background of your ImageView.

But the point of this way is Views dimensions. if your ImageView has incorrect dimensions, drawable gets Stretch. you must control it in pre-lollipop versions or Use some Views PercentRelativeLayout.

Hope it's helpful.

First : import svg to drawble : ((https://www.learn2crack.com/2016/02/android-studio-svg.html))

1-Right click on the drawable folder select New -> Vector Asset

enter image description here

2- The Vector Asset Studio provides you option to select the inbuilt Material icons or your own local svg file. You can override the default size if needed.

enter image description here

Second :if you want support from android API 7+ , you have to use Android Support Library acording ((https://stackoverflow.com/a/44713221/1140304))

1- add

vectorDrawables.useSupportLibrary = true

to your build.gradle file.

2-Use namespace in your layout that has imageView :

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

Third : set imageView with android:scaleType="fitXY" and use app:srcCompat

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

Fourth :if you want set svg in java code you have to add below line on oncreate methoed

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

I solve my problem just change size of vector image. From the first it was resource like:

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

And I've changed it to 150dp (size of ImageView in layout with this vector resource) like:

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

It's working for me.

For me, the reason that caused vectors to be converted to PNG was android:fillType="evenodd" attribute in my vector drawable. When I removed all occurrences of this attribute in my drawable (therefor the default nonzero fill type applied to the vector), next time the app was built everything was fine.

Aside from the top answers, on my side moving to ConstraintLayout instead of LinearLayout or FrameLayout removes the blurry on SVG vector drawable.

solved by adding

defaultConfig {
vectorDrawables.useSupportLibrary = true
}

to buil.gradle (module:app) file and for imageViews replace

android:src="..."

with

app:srcCompat="..."

hope help