15 minutes read

A Drawable object represents a graphic element. Any color, shape, gradient, vector, or raster image can be represented as a Drawable. It's even possible to implement your own.

Unlike Views, Drawables cannot interact directly with the user, but they have many other interesting capabilities that you will learn about in this topic. A Drawable can serve as a background, icon, scrollbar, cursor, slider track or thumb, or any other part of the user interface.

Let's walk through some basic graphics and their Drawable representations.

Color

A color is an integer holding four separate numbers that define the opacity (known as alpha), and the brightness of the red, green, and blue components. This means that any Kotlin integer is a valid color. For example, Purple square 0xFF8C5AFF.toInt() is fully opaque (FF), quite red (8C), a little green (5A), and very blue (FF). The same color can be expressed as #8C5AFF or #FF8C5AFF in XML. Note that the alpha component is optional, and, unlike Kotlin number literals, XML colors are implicitly opaque!

You probably remember from the "Values" topic that there are color resources. For instance, Lime square<color name="green_lime">#CDDC39</color> can be retrieved like this: ContextCompat.getColor(context, R.color.green_lime). Such an invocation will return the color from app resources and is equal to the 0xFFCDDC39.toInt() Kotlin expression.

We can use colors in many ways. Under the hood, having android:background="@color/green_lime", calling setBackgroundColor(0xFFCDDC39.toInt()), or setBackgroundResource(R.color.green_lime) are all equivalent to setBackground(ColorDrawable(0xFFCDDC39.toInt())).

Color selector

Unlike background which is a Drawable there are also several properties which aren't: backgroundTint of View, textColor, hintTextColor, and drawableTint of TextView.

A color is a suitable value for any of these properties but there's also another option: the ColorStateList also known as color selector. This defines several colors for different View states. You can see the selector example res/color/button_background_tint.xml below:

<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_pressed="true" android:color="#AC8AFF"/>
    <item android:state_focused="true" android:color="#6C3ADF"/>
    <item android:state_enabled="false" android:color="#AAAAAA"/>
    <item android:color="#8C5AFF"/>
</selector>

"Default" is indigo Purple square, disabled is gray Grey square, focused is darker indigo Dark purple square, and pressed is lighter indigo Light purple square. Here's a button tinted with this selector:

Button with custom colors

We can use this as a color like so: android:textColor="@color/button_background_tint". But it can also be used as a Drawable: android:background="@color/button_background_tint". This means ContextCompat.getColorStateList(context, R.color.button_background_tint) returns a ColorStateList, and ContextCompat.getDrawable(context, R.color.button_background_tint) returns a ColorStateListDrawable. A simple getColor will return the "default color" of a state list.

The selector from the above example can also be expressed in pure Kotlin. Note that the order is different here: the first argument enumerates all states, and the second one enumerates the corresponding colors. Negation, -some_state, is the equivalent for android:some_state="false".

ColorStateList(
    arrayOf(
        intArrayOf(android.R.attr.state_pressed),
        intArrayOf(android.R.attr.state_focused),
        intArrayOf(-android.R.attr.state_enabled),
        intArrayOf(),
    ),
    intArrayOf(
        0xFFAC8AFF.toInt(),
        0xFF6C3ADF.toInt(),
        0xFFAAAAAA.toInt(),
        0xFF8C5AFF.toInt(),
    )
)

Be aware that the order of states matters! Android looks for an appropriate state from top to bottom; the default state must be the last one.

Bitmap

A Bitmap is a matrix of colors (called pixels in this context). Any raster file format like PNG, GIF, WEBP, or JPEG can be decoded into a Bitmap with any of the BitmapFactory.decode* methods. In some situations, a Bitmap can be used as an intermediate buffer for drawing arbitrary graphics, but in most cases, it's just a photo inside ImageView.

As you might have already guessed, both setImageBitmap(bitmap) and setImageResource(R.drawable.mountains) will result in having a BitmapDrawable inside an ImageView.

Vector

Like an SVG image, a VectorDrawable consists of several paths that each form arbitrary shapes defined as points, lines, and curves on a Cartesian plane.

Here's how the XML Drawable resource for this iconBookmark iconlooks:

<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="24dp"
    android:height="24dp"
    android:viewportWidth="24"
    android:viewportHeight="24">
    <path
        android:fillColor="#FF000000"
        android:pathData="M4,4v16c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2V4c0,-1.1 -0.9,-2 -2,-2H6C4.9,2 4,2.9 4,4zM11,4h5v6.12c0,0.39 -0.42,0.63 -0.76,0.43L13.5,9.5l-1.74,1.05c-0.33,0.2 -0.76,-0.04 -0.76,-0.43V4z"/>
</vector>

Knowing vector internals is helpful but not required because in Android Studio you can just click File → New → Vector Asset and choose an existing SVG to import.

Unlike color or Bitmap, there isn't a special Vector class; there's just a VectorDrawable. Also, there's no public API for creating vector Drawables programmatically.

Shapes and gradients

Vector first appeared in Lollipop, but Android has supported simple shapes from the very beginning. We can create a rectangle with rounded corners, a horizontal gradient fill (from Purple square to Blue square), and a thin dark stroke Black square in the following way:

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <corners
        android:radius="8dp" />
    <gradient
        android:angle="0"
        android:startColor="#8C5AFF"
        android:endColor="#408BCF" />
    <stroke
        android:width="1dp"
        android:color="#333333" />
</shape>

Gradient shape with rounded corners and black bounds

See Shape drawable for a complete reference.

Ironically, ShapeDrawable is an unrelated class. <shape> actually gets inflated into a GradientDrawable, even if it's filled with a solid color. Here's the Kotlin version of the above shape:

GradientDrawable().apply {
    cornerRadius = dp(8f)
    orientation = GradientDrawable.Orientation.LEFT_RIGHT
    colors = intArrayOf(0xFF8C5AFF.toInt(), 0xFF408BCF.toInt())
    setStroke(dp(1), 0xFF333333.toInt())
}

This example uses two additional functions, Context.dp(value: Int): Int and Context.dp(value: Float): Float from the Splitties-dimensions library. They convert dips to physical pixels: pixels = dips * resources.displayMetrics.density.

9-patch

A 9-patch is a raster (PNG) image with stretchable areas. It is mainly used as a background because it automatically accommodates to the dimensions of the view. To illustrate this, let's take a peek at some patches from the Android SDK:

Set of pngs foe -patch drawable in folder

So, these files have .9.png extensions, they are valid PNG files, but there are some black dashes around the image. Patches have a special one-pixel border: black pixels on the left and at the top denote stretchable areas, and non-black pixels on the right and at the bottom indicate padding.

Example render of EditTexts which use stretched textfield_ images from the screenshot above:

Two EditTexts in a column

For more details, see the official docs on 9-patch and how to create patches in Android Studio. For the API, please refer to NinePatch and NinePatchDrawable.

In the above screenshot, there are some 9-patches called "default", "activated", "disabled", "focused" and "selected". In the next section, we'll look at how states like these can be used.

Drawable selector

You are already familiar with ColorStateList: a composite color that depends on the current state. A <selector> can also reference Drawables, so a StateListDrawable is a composite Drawable that also depends on the current state.

Here's a selector from the Android SDK that chooses from the patches above depending on the current view state, res/drawable/textfield_search.xml:

<?xml version="1.0" encoding="utf-8"?>
<!-- Apache 2.0 License header skipped… -->

<selector xmlns:android="http://schemas.android.com/apk/res/android">
    
    <item android:state_window_focused="false" android:state_enabled="true"
        android:drawable="@drawable/textfield_search_default" />
        
    <item android:state_pressed="true"
        android:drawable="@drawable/textfield_search_pressed" />
        
    <item android:state_enabled="true" android:state_focused="true"
        android:drawable="@drawable/textfield_search_selected" />
    
    <item android:drawable="@drawable/textfield_search_default" />
    
</selector>

The key difference between XML ColorStateList and an XML StateListDrawable is that the former lives in res/color and requires the android:color attribute, and the latter belongs to res/drawable and uses android:drawable.

When creating selectors programmatically, there's no constructor (states: Array<IntArray>, drawables: Array<Drawable>). Instead, we use the addState() method. Here's a Kotlin version of the above selector, which assumes that textfieldSearchPressed, textfieldSearchSelected, and textfieldSearchDefault have already been defined:

StateListDrawable().apply {
    addState(
        intArrayOf(-android.R.attr.state_window_focused, android.R.attr.state_enabled),
        textfieldSearchDefault
    )
    addState(
        intArrayOf(android.R.attr.state_pressed),
        textfieldSearchPressed
    )
    addState(
        intArrayOf(android.R.attr.state_enabled, android.R.attr.state_focused),
        textfieldSearchSelected
    )
    addState(
        intArrayOf(),
        textfieldSearchDefault
    )
}

Other Drawables

Button responds on a click with a ripple

Since the release of Lollipop, clickable elements like list items respond to clicks with a ripple. You can read more about this in the official RippleDrawable doc.

A LayerDrawable is a composite that draws its children one over another.

InsetDrawable is a decorator that adds some paddings to another Drawable.

Along with state, Drawables can also have a level: an integer in the 0..10_000 range. For example, battery, Wi-Fi, and mobile signal levels are LevelListDrawables:

Set of battery icon pictures with different charge level


There are also the RotateDrawable, the ScaleDrawable, and the ClipDrawable. The way these Drawables look is dependent on the level value, too.

Finally, for something that moves, you can check out the TransitionDrawable, the AnimationDrawable, or the AnimatedVectorDrawable, amongst others.

Conclusion

The vast majority of the graphic elements you can see in the Android OS and its applications are Drawables, and you have discovered how to instantiate a variety of them from both XML and Kotlin.

We'll dive deeper into the Drawable API and even implement our own later, but now it's time to practice what you've learned so far!

26 learners liked this piece of theory. 2 didn't like it. What about you?
Report a typo