Custom Drawable

Drawing actions without creating an extra layer in layout XMLs.

Maciej Najbar
4 min readJun 14, 2020
SpotOn Transact Inc.

I love our new designs! Recently I’ve been working on user interactions. One of them is presented on the GIF above. I wanted to create a custom Drawable, so I can set a background on any view I want without wrapping it with some custom ViewGroup. An extra layer means extra calculations.

I couldn’t find any guide nor article that would help me create a Drawable object that I would be able to set as a background via XML. Like a Ripple Drawable. Unfortunately if someone counts that in this article I solved that mistery I’ll heads up a little — I didn’t. I looked though how RippleDrawable is defined and I prepared my Drawable classes based on what I saw.

Divide and Counquer

All advanced animations look complicated until we find a way to disassemble them. What animations are present on this GIF?

There are three:

  1. Rounded Rectangle Bounce
  2. Alpha Mask Appearance
  3. Long-Click Filling Color

How a Ripple knows when animate? That was my first question. When I think about a View it comes to my mind onClickListener or onTouchEvent. Drawable doesn’t have such methods. It has a method named onStateChanged like this:

RippleDrawable.java

Rounded Rectangle Bounce

That was a starting point. I reflected this method in my class, removing the irrelevant states:

So first step is to override a method isStateful. Without that, the code from onStateChanged will never be triggered.
Next, I wanted to start the animation on finger released. Otherwise long click would start with a bouncing Drawable. The condition to start was: is enabled and was pressed but is not pressed anymore.

setClickActive(enabled && wasPressed && !pressed)

To start an animation I used an ObjectAnimator and took advantage of float type by starting a value from 0.99 and finishing on 1. This cheats an eye enough to see a fluent movement and I get an end-value different from start-value which helps me differentiate if the anim is in run without extra flags.

Alpha Mask Appearance

So the rectangle shrinks, but the animation also has a transparent mask showing on top of. The mask must be fully transparent at first and fade-in and out within the same time as previous animation.

This code is not a final touch but the POC that I went out of. Anyone may feel free to do the same thing.
Also, keep in mind that an ObjectAnimator uses reflection to trigger set methods, so always add a @Keep annotation, to prevent ProGuard from minifying them.

Using Custom Drawable

As I mentioned in the beginning of the article I wasn’t able to add a background drawable in a layout resource file, so I had to do it programmatically like this:

Cascade usage of drawables was possible only because of deriving from LayerDrawable class which is a well known <layer-list> node in drawable XMLs.

Long-Click Filling Color

I have a nice bouncing animation now that I can add to any of my Views. There’s only one more animation left to be done. Filling a color on long click.

I know how to react to states of the Drawable. The only difference is the start of the animation. I wouldn’t like the color to start filling right after I press a View, because I would loose that experience that, these are in fact, two different actions. There’s an easy way though. Object animator has a property startDelay that I experimented with to get that natural distinction between a simple click and a long click. To me 100ms is enough:

Conclusion

It’s possible and not that hard to create a custom Drawable. But is it worth it? Is it better than creating a custom ViewGroup? Every solution in programming has its pros and cons. It’s always our own decision which way we choose.

A custom Drawable:

  • Saves calculations of positioning
  • Doesn’t require to change all XMLs by adding a surrounding ViewGroup
  • Supports only limited number of states, that are already recognized by a View and passed to a Drawable

A custom ViewGroup:

  • Custom ViewGroup on the other hand can be used in an XML and a background Drawable can be added there rather than from the code
  • With custom ViewGroup you have more control by overriding onTouchEvent whereas custom Drawable only reacts to states

There must be a reason why Google doesn’t let us create a custom Drawable easily. There are no tutorials, no option to create a custom node like <ripple> has. Maybe it’s because it’s not the best choice? Or maybe soon Google give us a tool to do just that?

--

--