Sam
Sam

Reputation: 361

Vector Drawable in Layer List on Older Android Versions

On newer Android versions, the following code:

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item>
            <shape android:shape="oval">
                <solid android:color="#bdbdbd" />
                <size
                    android:width="60dp"
                    android:height="60dp" />
            </shape>
    </item>
    <item
        android:drawable="@drawable/ic_library_books_black_24dp"
        android:gravity="center"
        android:width="40dp"
        android:height="40dp"
        >
    </item>
</layer-list>

produces this flawlessly:

Vector drawable showing white lists on grey circular background

However, earlier Android versions (API 16 and 19, from what I've tested) do not like this at all and I get

E/AndroidRuntime: FATAL EXCEPTION: main
              Process: package.app, PID: 11490
              android.view.InflateException: Binary XML file line #26: Error inflating class ImageView

upon inflation. I have used app:srcCompat for all my ImageViews so there is no problem there.

Standard Vector Drawables also work fine, but when placed in a layer-list they cause mayhem. Are there any workarounds?

Upvotes: 35

Views: 19065

Answers (4)

Rapunzel Van Winkle
Rapunzel Van Winkle

Reputation: 5500

The width/height attributes for the vector drawable in your layer-list are only supported in API 23 (Marshmallow) and higher. If you look at your layer-list drawable in the Android Studio editor, these attributes should have yellow blocks around them along with a warning that this won't work reliably on older devices.

But I think you can get rid of the warning and achieve the same centering effect like this:

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item>
        <shape android:shape="oval">
            <solid android:color="#bdbdbd" />
            <size
                android:width="60dp"
                android:height="60dp" />
        </shape>
    </item>
    <item
        android:drawable="@drawable/ic_library_books_black_24dp"
        android:top="10dp"
        android:bottom="10dp"
        android:left="10dp"
        android:right="10dp">
    </item>
</layer-list>

I tested this on an old API 15 phone and it worked fine. I hope this works for you too.

Update:

In a previous version of this answer, I'd advised against using vectorDrawables.useSupportLibrary = true with layer lists, because it caused crashes. However, I've recently learned about a workaround that seems to fix the crash (while avoiding the fat auto-generated png files that @android developer correctly mentioned). Here's a summary of what needs to be done for it to work correctly:

Be sure to use srcCompat in your xml.

    <android.support.v7.widget.AppCompatImageView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    app:srcCompat="@drawable/layer_list_with_svg" />

Add 'vectorDrawables.useSupportLibrary' to app/build.gradle

 android {
   defaultConfig {
     vectorDrawables.useSupportLibrary = true
   }
 }

Add 'setCompatVectorFromResourcesEnabled(true)' to onCreate

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

Why is all of this necessary?

As a more knowledgable person explained it to me, this has to do with how Android loads up the compat vector drawables. If you're using vectorDrawables.useSupportLibrary = true then image views will load up VectorDrawableCompat drawables when you use app:srcCompat. When your layer list drawable is inflated, there's no way for it to figure out how to create those referenced vector drawables. But if you turn on setCompatVectorFromResourcesEnabled it will try to hook the vector drawable loading in at a much lower level than image views and their app:srcCompat attribute, so it's then able to figure out how to load the vector drawables referenced in the layer list.

Upvotes: 34

android developer
android developer

Reputation: 115980

The support for VectorDrawble on old Android versions is quite limited, assuming you use vectorDrawables.useSupportLibrary = true :

You can use VectorDrawableCompat back to API level 7 and AnimatedVectorDrawableCompat on all devices running Android 5.0 (API level 11) and higher. The way Android loads drawables, not every place that accepts a drawable ID, such as in an XML file, supports loading vector drawables. The android.support.v7.appcompat package has added a number of features to make it easy to use vector drawables. Firstly, when you use android.support.v7.appcompat package with ImageView or with subclasses such as ImageButton and FloatingActionButton, you can use the new app:srcCompat attribute to reference vector drawables as well as any other drawable available to android:src

Even in code, you are limited:

To change drawables at runtime, you can use the setImageResource() method as before. Using AppCompat and app:srcCompat is the most foolproof method of integrating vector drawables into your app.

What can you do?

Few possible solutions:

  1. have alternative drawable for the VectorDrawable that you use in the LayerDrawable. Put it in res/drawable-xxhdpi, for example, and the VectorDrawable into res/drawable-anydpi .

  2. don't use vectorDrawables.useSupportLibrary = true till you have the app have a minSdk that is capable of using VectorDrawable. This will work because the IDE will generate PNG files for you.

  3. create the layerList programmatically (example here), and in order to put the VectorDrawable, use AppCompatResources.getDrawable . You might also be able to do it with XML, except the part of putting the VectorDrawable.

BTW, the reason that it's limited, is that Google failed to make it efficient and without issues. You can get to their original attempt of having full support, by using AppCompatDelegate.setCompatVectorFromResourcesEnabled(true) , but as the docs say, you are taking a risk here:

This feature defaults to disabled, since enabling it can cause issues with memory usage, and problems updating Configuration instances. If you update the configuration manually, then you probably do not want to enable this. You have been warned.

Upvotes: 1

Lewis McGeary
Lewis McGeary

Reputation: 7932

This answer draws on the article AppCompat - Age of the Vectors by Chris Banes(who works on the Support Library). For this question we're looking specifically at the section titled The 'magic' way.

The crash you're experiencing is because the Support Library only allows some ways of using VectorDrawables by default, and layer-list is not one of them.

There is a specific code block you can add to the top of your Activity to enable other VectorDrawable use such as <layer-list>:

static {
    AppCompatDelegate.setCompatVectorFromResourcesEnabled(true);
}

Note: the linked article contains a typo in this method name, using "FromSources", it should be "FromResources" as shown above.

You would need to add this to each Activity where you want to use such drawables, or perhaps include it in a BaseActivity class that your other Activities extend from.

Per the article, this should mean the following will now work:

DrawableContainers which reference other drawables resources which contain only a vector resource.

...StateListDrawable...InsetDrawable, LayerDrawable, LevelListDrawable and RotateDrawable.

It should be noted though, this method is heavily couched with the word 'may', this may work, and it is not enabled by default, so be aware and check it's really working for you!

Now there's actually another dimension to this question, credit to other users Selim Ajimi and Rapunzel Van Winkle for addressing this in their answers. <layer-list> has some different behaviour between the API's, in particular the width and height attributes of your <item> only being supported in API 23+. This is not the cause of your crash, nor will it cause your app to crash, but will mean that your image will not look as intended once you have it functioning in earlier APIs.

The suggestion from Rapunzel Van Winkle does indeed seem to be a good way to position the drawable correctly across APIs (tested on API 16 and 24):

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item>
        <shape android:shape="oval">
            <solid android:color="#bdbdbd" />
            <size
                android:width="60dp"
                android:height="60dp" />
        </shape>
    </item>
    <item
        android:drawable="@drawable/ic_library_books_black_24dp"
        android:top="10dp"
        android:bottom="10dp"
        android:left="10dp"
        android:right="10dp"
        >
    </item>
</layer-list>

Upvotes: 9

Selim Ajimi
Selim Ajimi

Reputation: 344

<?xml version="1.0" encoding="utf-8"?>
<layer-list
    xmlns:android="http://schemas.android.com/apk/res/android" >
    <item
        android:drawable="@[package:]drawable/drawable_resource"
        android:id="@[+][package:]id/resource_name"
        android:top="dimension"
        android:right="dimension"
        android:bottom="dimension"
        android:left="dimension" />
</layer-list>

As you can see here doesn't have width/height attributes...

You can append Bitmap to item I think this is the best solution

Upvotes: 1

Related Questions