Custom Drawable — Part 3

In the last part of this series, we will make the drawable animated between states. Here is the result we want:

Ok, let’s do it.


As usual, we need to make some changes on StateBorderDrawable class.

  • First, we add a new duration parameter to constructor method:
public class AnimatedStateBorderDrawable extends Drawable {

    private boolean mRunning = false;
    private long mStartTime;
    private int mAnimDuration;

    Paint mPaint;
    ColorStateList mColorStateList;
    int mPrevColor;
    int mMiddleColor;
    int mCurColor;
    int mBorderWidth;
    int mBorderRadius;

    RectF mRect;
    Path mPath;

    public BorderDrawable(ColorStateList colorStateList, int borderWidth, int borderRadius, int duration){
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);

        mPath = new Path();

        mRect = new RectF();

        mColorStateList = colorStateList;
        mCurColor = mColorStateList.getDefaultColor();
        mPrevColor = mCurColor;
        mBorderWidth = borderWidth;
        mBorderRadius = borderRadius;
        mAnimDuration = duration;

You can see that I added some new member variable like mPrevColor, mCurColor, mMiddeColor. Because we will animate the color between 2 states, so we need to know the color of previous and current state. Some variable like mRunning, mStartTime also needed for storing animation’s data.

  • Next, we have to implement interface. There are 3 methods:
public boolean isRunning() {
    return mRunning;

public void start() {
    scheduleSelf(mUpdater, SystemClock.uptimeMillis() + FRAME_DURATION);

public void stop() {
    mRunning = false;

isRunning() is pretty forward, so let’s talk about start() method. When we call start() method to start running the animation, first we have to call resetAnimation() to reset all animation’s data:

private void resetAnimation(){
    mStartTime = SystemClock.uptimeMillis();
    mMiddleColor = mPrevColor;

You will see that there are 2 variables need to be updated: mStartTime for animation start running time, and mMiddleColor for color will be drawn when animation running. Then, we will schedule a Runnable will be run after a specific duration to update animation’s progress and invalidate drawable:

private final Runnable mUpdater = new Runnable() {

    public void run() {


private void update(){
    long curTime = SystemClock.uptimeMillis();
    float progress = Math.min(1f, (float) (curTime - mStartTime) / mAnimDuration);
    mMiddleColor = getMiddleColor(mPrevColor, mCurColor, progress);

    if(progress == 1f)
        mRunning = false;

        scheduleSelf(mUpdater, SystemClock.uptimeMillis() + FRAME_DURATION);


In the update() method, we will calculate the mMiddleColor based on the animation’s progress and the color of 2 states. Then we check if the animation is completed to continue schedule mUpdater or not.

  • Next, we update onStateChange(int[]) and drawn(Canvas) methods:
protected boolean onStateChange(int[] state) {
    int color = mColorStateList.getColorForState(state, mCurColor);

    if(mCurColor != color){
        if(mAnimDuration > 0){
            mPrevColor = isRunning() ? mMiddleColor : mCurColor;
            mCurColor = color;
            mPrevColor = color;
            mCurColor = color;
         return true;

    return false;

public void draw(Canvas canvas) {
    mPaint.setColor(isRunning() ? mMiddleColor : mCurColor);
    canvas.drawPath(mPath, mPaint);
  • And we also override jumpToCurrentState() and scheduleSelf(Runnable, long) methods:
public void jumpToCurrentState() {

public void scheduleSelf(Runnable what, long when) {
    mRunning = true;
    super.scheduleSelf(what, when);

jumpToCurrentState() method will be called when the view want to skip all the drawable’s animation, so we will call stop() method to stop animation if it’s running.


We just have to change some few point in the StateBorderImageView class.

  • First is the init() method. Change from StateBorderDrawable to AnimatedStateBorderDrawable:
mBorder = new AnimatedStateBorderDrawable(colorStateList, 
        getPaddingLeft() / 2, 
  • Next, we need to override jumpDrawablesToCurrentState() method to notify drawable when the view want to skip all animations.
public void jumpDrawablesToCurrentState() {
  • That’s it. Now we can add this AnimatedStateBorderImageView to XML:

Let’s run and see the result.

That is the last of this tutorial series. Although the code is simple, but now you know the basic of implementing a fully animated drawable.

The source code is available on Github.