Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 222
Sam Avatar asked Mar 19 '17 10:03

Sam


People also ask

What are vector Drawables in android?

A VectorDrawable is a vector graphic defined in an XML file as a set of points, lines, and curves along with its associated color information. The major advantage of using a vector drawable is image scalability.

Is SVG compatible with android?

Android Studio includes a tool called Vector Asset Studio that helps you add material icons and import Scalable Vector Graphic (SVG) and Adobe Photoshop Document (PSD) files into your project as vector drawable resources.

What is Layer list in android?

Layer list. A LayerDrawable is a drawable object that manages an array of other drawables. Each drawable in the list is drawn in the order of the list—the last drawable in the list is drawn on top.


2 Answers

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.

like image 69
Rapunzel Van Winkle Avatar answered Oct 04 '22 18:10

Rapunzel Van Winkle


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> 
like image 27
Lewis McGeary Avatar answered Oct 04 '22 18:10

Lewis McGeary