Why isn't my vector drawable scaling as expected?

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.

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.


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


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!