Often when creating interfaces in Android, it can be more efficient to have a single
.xml drawable and recolouring it according to requirements, instead of trying to include all possible colours in advance. Similarly, it can be more efficient to replace the drawable used inside another drawable dynamically. However, if this needs to be done multiple times within one drawable it becomes a bit more complex, as any modifications will affect the entire drawable.
This post is also available as a Gist.
Creating the drawable XML
For this example, a simple drawable with a coloured circle background (ID
background_circle) and a vector image (ID
foreground_icon) will be used. The following
.xml should be placed at
<?xml version="1.0" encoding="utf-8"?> <layer-list xmlns:android="http://schemas.android.com/apk/res/android"> <item android:id="@+id/background_circle"> <shape android:shape="oval"> <solid android:color="@color/orange" /> </shape> </item> <item android:id="@+id/foreground_icon"> <shape> <solid android:color="@android:color/transparent" /> </shape> </item> </layer-list>
Recolouring part of the drawable
The first half of the changes to be made are changing the background colour of our drawable.
First, get a reference to your target ImageView (either in a layout or inflated dynamically), and retrieve its drawable as a LayerDrawable (as we need to handle layers):
val layerDrawable = findViewById(R.id.my_drawable).drawable as LayerDrawable
Next, get the background layer specifically using the
background_circle ID from earlier:
val backgroundDrawable = layerDrawable.findDrawableByLayerId(R.id.background_circle) as GradientDrawable
The colour can now be set using the normal
Changing part of the drawable
The second half of the changes is changing the foreground drawable.
layerDrawable from earlier, set the
foreground_icon to an existing drawable resource:
layerDrawable.setDrawableByLayerId(R.id.foreground_icon, ContextCompat.getDrawable(context, R.drawable.new_foreground_icon))
That’s it! Both the composite drawable and background color have been changed, and this technique can be used for as many layers as required.
The ability to create complex dynamic drawables using layer-lists is extremely beneficial when attempting to minimise app size, as it means large areas of an app can use the same base layout, and just customise the small parts that are different.
For example, in the real world example this post is based on, a total of 5 foreground icons and 7 background colours were possible, meaning a total of 35 different icons were required. Using drawable layer modification, this could all be done within a few lines of code, instead of tens of resources! Much more advanced layer modifications are also possible, to create more complex differences.
All code used in this post is available as a Gist.