In the realm of digital interface design, the visual elements are crucial for engaging users and enhancing their experience. Among these elements, drawables in Android play a pivotal role in shaping the aesthetics and functionality of applications. While basic drawables provide a foundation, delving into the realm of advanced Android drawables unlocks a spectrum of creative possibilities. In this exploration, we embark on a journey to uncover the untapped potential of advanced Android drawables, showcasing their ability to elevate UI design and enrich the overall user experience.
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
Advanced Android drawables offer a realm of possibilities for designers to create captivating and innovative UI designs. By mastering complex shapes, dynamic gradients, interactive feedback, and specialized effects, designers can craft interfaces that engage and delight users. As you explore the world of digital design, consider the immense potential of advanced drawables to elevate your designs and create exceptional user experiences.