Android
setCompoundDrawables
Compound Drawable
troubleshooting
UI development

Calling setCompoundDrawables doesn't display the Compound Drawable

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

If you have ever called setCompoundDrawables() on a TextView or EditText and nothing appeared, you are not alone. This is one of the most common Android UI surprises, and the root cause is almost always the same: the drawable has no bounds. Understanding why Android requires explicit bounds and knowing the simpler alternative method will save you hours of debugging.

Why setBounds() Is Required

Android's setCompoundDrawables() method does not automatically measure or size the drawable you pass in. Unlike loading a drawable into an ImageView, where the system infers dimensions from the resource, compound drawables are positioned inline with text and Android needs you to explicitly define how much space the drawable should occupy.

When you call setCompoundDrawables(left, top, right, bottom), Android reads each drawable's getBounds() to determine its width and height. If you never called setBounds(), the bounds default to a zero-width, zero-height rectangle, so the drawable is technically there but invisible.

Here is the broken version in Java:

java
Drawable icon = ContextCompat.getDrawable(context, R.drawable.ic_search);
// Bug: no setBounds() call, drawable will not appear
textView.setCompoundDrawables(icon, null, null, null);

And here is the fix:

java
Drawable icon = ContextCompat.getDrawable(context, R.drawable.ic_search);
icon.setBounds(0, 0, icon.getIntrinsicWidth(), icon.getIntrinsicHeight());
textView.setCompoundDrawables(icon, null, null, null);

The setBounds(left, top, right, bottom) call defines the drawing rectangle. Using getIntrinsicWidth() and getIntrinsicHeight() tells Android to use the drawable's natural pixel dimensions.

The Easier Alternative: setCompoundDrawablesWithIntrinsicBounds

Because forgetting setBounds() is such a common mistake, Android provides setCompoundDrawablesWithIntrinsicBounds(). This method automatically calls setBounds() using each drawable's intrinsic dimensions, so you can skip that step entirely.

In Java:

java
Drawable icon = ContextCompat.getDrawable(context, R.drawable.ic_search);
textView.setCompoundDrawablesWithIntrinsicBounds(icon, null, null, null);

There is also an overload that accepts resource IDs directly, which is even more concise:

java
textView.setCompoundDrawablesWithIntrinsicBounds(
    R.drawable.ic_search, 0, 0, 0
);

In Kotlin, the same calls look like this:

kotlin
1val icon = ContextCompat.getDrawable(this, R.drawable.ic_search)
2textView.setCompoundDrawablesWithIntrinsicBounds(icon, null, null, null)
3
4// Or with resource IDs
5textView.setCompoundDrawablesWithIntrinsicBounds(
6    R.drawable.ic_search, 0, 0, 0
7)

For RTL (right-to-left) language support, prefer setCompoundDrawablesRelativeWithIntrinsicBounds(), which uses start/end instead of left/right:

kotlin
textView.setCompoundDrawablesRelativeWithIntrinsicBounds(
    R.drawable.ic_search, 0, 0, 0
)

Positioning: Left, Top, Right, Bottom

The four parameters in setCompoundDrawables(left, top, right, bottom) control where the drawable appears relative to the text. Pass null (or 0 for the resource ID overload) for any position you do not need.

java
1// Icon on the left only
2textView.setCompoundDrawablesWithIntrinsicBounds(icon, null, null, null);
3
4// Icon on the right only
5textView.setCompoundDrawablesWithIntrinsicBounds(null, null, icon, null);
6
7// Icons on both left and right
8textView.setCompoundDrawablesWithIntrinsicBounds(leftIcon, null, rightIcon, null);

You can control the gap between the drawable and the text with setCompoundDrawablePadding():

kotlin
textView.compoundDrawablePadding = 16 // 16 pixels of spacing

Tinting and Custom Sizing

To tint a compound drawable, use DrawableCompat.setTint() before attaching it:

kotlin
1val icon = ContextCompat.getDrawable(this, R.drawable.ic_search)!!
2val wrapped = DrawableCompat.wrap(icon).mutate()
3DrawableCompat.setTint(wrapped, ContextCompat.getColor(this, R.color.primary))
4textView.setCompoundDrawablesWithIntrinsicBounds(wrapped, null, null, null)

If you need a custom size instead of the intrinsic dimensions, call setBounds() with your desired values and use setCompoundDrawables():

kotlin
1val icon = ContextCompat.getDrawable(this, R.drawable.ic_search)!!
2val size = (24 * resources.displayMetrics.density).toInt()
3icon.setBounds(0, 0, size, size)
4textView.setCompoundDrawables(icon, null, null, null)

Common Pitfalls

  • Forgetting to call setBounds() when using setCompoundDrawables(), resulting in an invisible drawable.
  • Using setCompoundDrawables() with left/right parameters in an RTL locale instead of the relative variants, causing the icon to appear on the wrong side.
  • Passing a null drawable without realizing ContextCompat.getDrawable() can return null if the resource ID is invalid.
  • Calling setTint() without mutate(), which can change the tint on every view sharing that drawable.
  • Setting compound drawable padding in dp but passing raw integer pixels, leading to inconsistent spacing across screen densities.

Summary

  • setCompoundDrawables() requires you to call setBounds() on each drawable first, otherwise the drawable renders with zero size and is invisible.
  • setCompoundDrawablesWithIntrinsicBounds() handles bounds automatically and is the preferred method for most use cases.
  • Use the Relative variants (setCompoundDrawablesRelativeWithIntrinsicBounds()) to correctly support RTL layouts.
  • Control spacing with setCompoundDrawablePadding() and tint with DrawableCompat.setTint() after calling mutate().
  • For custom-sized drawables, manually set bounds to your desired dimensions and use the base setCompoundDrawables() method.

Course illustration
Course illustration

All Rights Reserved.