112

I am attempting to use vector drawables in my Android app. From http://developer.android.com/training/material/drawables.html (emphasis mine):

In Android 5.0 (API Level 21) and above, you can define vector drawables, which scale without losing definition.

Using this drawable:

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

and this ImageView:

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

produces this blurry image when attempting to display the icon at 400dp (on a largish high-res circa 2015 mobile device running lollipop):

blurryBellIcon

Changing the width and height in the definition of the vector drawable to 200dp significantly improves the situation at the 400dp rendered size. However, setting this as a drawable for a TextView element (i.e. icon to the left of the text) now creates a huge icon.

My questions:

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?

2) Is it possible to use a single vector drawable which works as a 24dp drawable on a TextView but scales up well to use as much larger images too? E.g. how do I avoid creating multiple vector drawables of different sizes and instead use one which scales to my rendered requirements?

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

Additional details:

  • Device is running API 22
  • Using Android Studio v1.5.1 with Gradle version 1.5.0
  • Manifest is compile and target level 23, min level 15. I've also tried moving min level to 21, but this made no difference.
  • Decompiling the APK (with min level set to 21) shows a single XML resource in the drawable folder. No rasterized images are produced.
Chris Knight
  • 24,333
  • 24
  • 88
  • 134
  • Just to be clear. You are using Android studio? What version of Android Studio and what version of the Gradle plugin? When I right click the drawable folder in Android Studio and choose `New -> Vector Asset` it drops the vector image XML in my drawable folder. However if I use apktool to unpack the APK that gets built I see that the XML files are in `drawable-anydpi-v21` and scale correctly on API 21+ devices. The raster files are placed in the `drawable--v4` folders and not used on API 21+ devices (based on the fact that they scale correctly) – Cory Charlton Jan 22 '16 at 17:14
  • @Cory Charlton. Curious. I'm using Studio 1.5.1 with Gradle 1.5.0. After changing min level to 21, the only place the image appears in the APK is the `drawable` folder. The width/height in the vector drawable xml file is 24dp. Specifiying an ImageView of 400dp heigh/width is definitely creating a poorly scaled image. – Chris Knight Jan 22 '16 at 19:59
  • That's what I would expect. The only reason I end up with the `drawable-anydpi-v21` is because my `minSdkVersion` is less than 21. Any change in behavior if you set the `minSdkVersion` to less than 21? What about moving the XML to `drawable-anydpi`? I wouldn't expect there to be a change but I would also expect your vector image to be scaling correctly... – Cory Charlton Jan 22 '16 at 20:03
  • Changing min version back to 15 produces the same results as you. A single xml file in `drawable-anydpi-v21` with various rasterized images in the mdi/hdpi/etc. folders. No change to the end rendered result though. – Chris Knight Jan 22 '16 at 20:14
  • Very odd... I modified one of my apps using your image and it works fine (see my edited answer) – Cory Charlton Jan 22 '16 at 20:41
  • Actually take that back screenshot was on 6.0 (23). 5.0.2 (21) emulator looks jacked... – Cory Charlton Jan 22 '16 at 20:44
  • Very glad to see this question (and discussions). I'm seeing the very same. So it looks like the scaling of vector graphics is not supported in v21, only v23+ :( – 34m0 Feb 03 '16 at 10:02
  • Well, more precisely, the graphics will scale at compile time in v21 (with the right width attribute), just not run time (scaling above the width attribute). – Chris Knight Feb 04 '16 at 22:11

10 Answers10

117

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.

Daniel Nugent
  • 43,104
  • 15
  • 109
  • 137
Taehyun Park
  • 1,352
  • 1
  • 9
  • 11
  • 37
    Okay Google, it's absolutely terrible that we must copy-paste **identical drawables with same viewports and paths** which have only different sizes. – Miha_x64 Apr 19 '17 at 16:30
  • This fixed it on the device which was showing my logo pixelated, but on the other unit, where everything was OK before, it's now the wrong aspect ratio. I don't understand why this is a problem, it's a vector! Not pixels! :P – tplive Nov 18 '17 at 12:58
  • 2
    @Harish Gyanani, android:scaleType="fitXY" solves the problem but what about dynamical and not squared icons? – Manuela Jan 29 '19 at 16:50
  • android:scaleType="fitCenter" worked for me instead of android:scaleType="fitXY". – Tajveer Singh Nijjar Mar 10 '21 at 01:02
31

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

Cory Charlton
  • 8,868
  • 4
  • 48
  • 68
  • 1
    Thanks Cory, I had seen that documentation and also found it not very useful. My device is Lollipop (v22) and yet I can't get the graphic to scale well, above the 24dp specified in the vector drawable, regardless if the heigh/weight are exactly specified in the ImageView or not. I've tested a manifest with target and compile level 23 and min level 21 yet it won't smoothly upscale my vector drawable without me changing the width/height in the drawable itself. I don't really want to create multiple drawables whose only difference is height/width! – Chris Knight Jan 22 '16 at 07:08
  • 1
    Thanks for the confirmation and really appreciate your help. As you've demonstrated, it clearly should work as I expect it. Using your exact code produces the same result as I originally had. Frustrating! – Chris Knight Jan 22 '16 at 21:22
  • 1
    UPDATE: Ran my code against emulator with API 22 and I still get the same result. Ran my code against emulator with API 23 and, tada!, it works. Looks like the upscaling is only working on API 23+ devices. Tried changing the target SDK version but that made no difference. – Chris Knight Jan 22 '16 at 21:25
  • Did you find a solution for this issue? – dazza5000 Mar 08 '19 at 16:08
  • @corycharlton is it okay to remove `width` `height` `viewport` `height` and `width` from `xml` ? – VINNUSAURUS Apr 04 '19 at 19:08
13

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!

user3210008
  • 766
  • 6
  • 7
  • Good approach, but, AppCompat 25.4 (and maybe earlier) gives a lint error "can only be called from the same library group". The build tools I'm using still let it build, but I'm not sure if there are runtime consequences. About to test. – androidguy Nov 07 '17 at 22:06
  • can only be called from the same library group, remove this error by adding this: lintOptions { disable 'RestrictedApi' } – Usama Saeed US Jul 16 '19 at 15:33
9
  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.

miva2
  • 2,111
  • 25
  • 34
emirua
  • 498
  • 5
  • 14
  • Thanks Emiura. I'd be interested in how you accomplished multiple rendered sizes using the same drawable. Using a 24dp drawable (for test purposes) I've changed my ImageView to havea specified width and height of 400dp and run on my Lollipop device (v22) yet it is still rendering poorly, like a rastered upscaled image rather than a vector image. Also changed my manifest to have target and compile against v23 and min version 21. No difference. – Chris Knight Jan 22 '16 at 07:12
  • In this case you would only need to use the largest size in your vector drawable and then specify the 24dp size in your text view. – emirua Jan 23 '16 at 02:52
  • I see now that you get blurry images when you enlarge with very large differences between the size in the vector drawable and the size in the layout in Lollipop. However, It's still so much more neat just setting the largest size rather than having bunch of files for different screen sizes ;). – emirua Jan 23 '16 at 03:05
  • "_you get blurry images when you enlarge with very large differences between the size in the vector drawable and the size in the layout_" how large are we talking? – alpharoz Jun 22 '21 at 22:54
5

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.

Djek-Grif
  • 1,391
  • 18
  • 18
4

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.

Mahdi Astanei
  • 1,905
  • 1
  • 17
  • 30
2

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);
    }
Milad Ahmadi
  • 1,019
  • 12
  • 19
  • Does your solution work on API 21 and above? user3210008 above mentions that the implementation is not used on API 21 and above. – AJW Aug 09 '18 at 04:14
2

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.

Mahozad
  • 18,032
  • 13
  • 118
  • 133
0

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

Bitwise DEVS
  • 2,858
  • 4
  • 24
  • 67
0

solved by adding

defaultConfig {
    vectorDrawables.useSupportLibrary = true
}

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

android:src="..."

with

app:srcCompat="..."

hope help

ahzare
  • 1
  • 1