How to implement Pie Progress View in Android

I will be showing how to implement a Pie Progress View in an Android app. I’ve been asked asked to develop an Android app which contained a progress view in shape of a pie chart to show a countdown progress, so the view has to update every second.

Something like this:

screen-shot-2016-10-05-at-4-31-06-pm

To achieve this without reinventing the wheel, I fortunately found that David had created this drawable as you can see here.

So I will be showing how to implement his class, add it to an ImageView, change its color and update the progress.

First of all, we have to copy that class into our project. Once that is done, we will create an ImageView in the xml where we want to show the Pie Progress View:

<ImageView                  
android:id="@+id/time_progress"                  
android:layout_width="40dp"                  
android:layout_height="40dp"/>

In our activity’s or fragment’s onCreate we will create a new instance of the PieProgressDrawable class and set it’s color (in this case to white):

PieProgressDrawable pieProgressDrawable = new PieProgressDrawable();
pieProgressDrawable.setColor(ContextCompat.getColor(this, R.color.white));

Now we will set this drawable to our XML ImageView:

ImageView timeProgress = (ImageView) findViewById(R.id.time_progress);
timeProgress.setImageDrawable(pieProgressDrawable);

Now, let’s assume we have a CountDownTimer implemented which every few seconds calls a method passing a new progress to update the Pie Progress View:

    public void updateTime(int progress) {
        pieProgressDrawable.setLevel(progress);
        timeProgress.invalidate();
    }

pieProgressDrawable.setLevel(progress)  will set the drawable’s progress level. The level is then used to create a drawable with specific angles, colors and shapes to represent the Pie Drawable as you can see in PieProgressDrawable class method:

    @Override
    protected boolean onLevelChange(int level) {
        final float drawTo = START_ANGLE + ((float)360*level)/100f;
        boolean update = drawTo != mDrawTo;
        mDrawTo = drawTo;
        return update;
    }

Finally, in order to update the UI to show the new progress we need to redraw the drawable by calling: timeProgress.invalidate();

That’s it! Here’s the complete PieProgressDrawable class in case the gist is not available anymore:

import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.util.DisplayMetrics;

/**
 * A PieProgressDrawable does this:
 * <a href="http://stackoverflow.com/questions/12458476/how-to-create-circular-progress-barpie-chart-like-indicator-android">Circular Progress Bar Android</a>
 */
public class PieProgressDrawable extends Drawable {

    Paint mPaint;
    RectF mBoundsF;
    RectF mInnerBoundsF;
    final float START_ANGLE = 0.f;
    float mDrawTo;

    public PieProgressDrawable() {
        super();
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    }

    /**
     * Set the border width.
     * @param widthDp in dip for the pie border
     */
    public void setBorderWidth(float widthDp, DisplayMetrics dm) {
        float borderWidth = widthDp * dm.density;
        mPaint.setStrokeWidth(borderWidth);
    }

    /**
     * @param color you want the pie to be drawn in
     */
    public void setColor(int color) {
        mPaint.setColor(color);
    }

    @Override
    public void draw(Canvas canvas) {
        // Rotate the canvas around the center of the pie by 90 degrees
        // counter clockwise so the pie stars at 12 o'clock.
        canvas.rotate(-90f, getBounds().centerX(), getBounds().centerY());
        mPaint.setStyle(Paint.Style.STROKE);
        canvas.drawOval(mBoundsF, mPaint);
        mPaint.setStyle(Paint.Style.FILL);
        canvas.drawArc(mInnerBoundsF, START_ANGLE, mDrawTo, true, mPaint);

        // Draw inner oval and text on top of the pie (or add any other
        // decorations such as a stroke) here..
        // Don't forget to rotate the canvas back if you plan to add text!
        // ...
    }

    @Override
    protected void onBoundsChange(Rect bounds) {
        super.onBoundsChange(bounds);
        mBoundsF = mInnerBoundsF = new RectF(bounds);
        final int halfBorder = (int) (mPaint.getStrokeWidth()/2f + 0.5f);
        mInnerBoundsF.inset(halfBorder, halfBorder);
    }

    @Override
    protected boolean onLevelChange(int level) {
        final float drawTo = START_ANGLE + ((float)360*level)/100f;
        boolean update = drawTo != mDrawTo;
        mDrawTo = drawTo;
        return update;
    }

    @Override
    public void setAlpha(int alpha) {
        mPaint.setAlpha(alpha);
    }

    @Override
    public void setColorFilter(ColorFilter cf) {
        mPaint.setColorFilter(cf);
    }

    @Override
    public int getOpacity() {
        return mPaint.getAlpha();
    }
}