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, 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, <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
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 icon
<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
<?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>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:
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:
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
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:
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!