Android Circle Progress Indicator

Published 11/01/2018 01:27 PM   |    Updated 11/14/2018 08:28 AM
This article originally appeared on Nov. 25, 2014.
 
Mobile applications typically get data from a mobile device’s network connections. At times, that connection is inconsistent and unreliable. Therefore, most apps have some sort of progress meter. Regardless if the app is sending, receiving or just plain displaying data, we want to inform the user of its progress. In place of the old progress bars, software developers now frequently use a circle indicator.
 
We were in need of a way to represent a wide range of data being displayed in a cross-platform mobile application. Our different attempts at a solution culminated in a circle progress view — something that allows us to clearly state progress while giving additional information to the user. Naturally, a circle allows us to encompass our progress visually around text data.
 

Solution


Working in unison, our designers and developers created the Circle Progress Indicator (CPI). This version of the CPI was developed for the Android platform. The sample project was developed in Android Studio and can be downloaded from our GitHub:
 
 
Progress Indicator
 

Technical details

 

Curved text label

 
Note: This section requires a working knowledge of developing for Android.
 
Working with the CPI is fairly straightforward. The first piece we need to look at is the CurvedTextView object. It’s a custom extension of the Android TextView widget and can be found in the root package of the example app. The big takeaway here is that extending TextView gives us access to the overridden onDraw method. Here’s where we do the calculations to best display the text centered on the ring. Using the screen size, length of the text in the box, offsets and radius of our circle, we create the arc and draw it to the screen:
 
@Override
protected void onDraw(Canvas canvas) {
int centerXOnView = getWidth() / 2;
	int centerYOnView = getHeight() / 2;

	int viewXCenterOnScreen = getLeft() + centerXOnView;
	int viewYCenterOnScreen = getTop() + centerYOnView;

float threeDpPad = getResources().getDimension(R.dimen.three_dp);
	float rad = getResources().getDimension(R.dimen.seventy_dp);

	int leftOffset = (int) (viewXCenterOnScreen - (rad + (threeDpPad * 4)));
	int topOffset = (int) (viewYCenterOnScreen - (rad + (threeDpPad * 3)));
	int rightOffset = (int) (viewXCenterOnScreen + (rad + (threeDpPad * 4)));
	int bottomOffset = (int) (viewYCenterOnScreen + (rad + threeDpPad));

	RectF oval = new RectF(leftOffset, topOffset, rightOffset, bottomOffset);

	int textLength = getText().length();
	if ((textLength % 2) != 0) {
		textLength = textLength + 1;
	}
	this.myArc.addArc(oval, -90 - (textLength * 2), 90 + textLength + 10);

	canvas.drawTextOnPath((String) getText(), this.myArc, 0, 10, this.mPaintText);
	invalidate();
}	
 
When you declare your custom text view in your activity’s layout, ensure you call it by the name you gave it, including package. For our example app, that looks like this:
 
<com.cardinalsolutions.progressindicator.CurvedTextView
android:id="@+id/compliance_curved_text"
	android:layout_width="match_parent"
	android:layout_height="match_parent"
	android:gravity="center"
	android:layout_alignParentTop="true"
	android:textSize="@dimen/fifteen_sp"
	android:text="@string/curved_text_value"
	android:textStyle="bold"
	android:paddingTop="@dimen/ten_dp" />
 
If you’re going to set the text on your new widget dynamically, you’ll need to declare in your activity as well. Make sure you declare your new custom object (not the default TextView):
 
private CurvedTextView mCurvedTextView;


mCurvedTextView.setText(Dynamic Text);
 

Custom rings

The foreground and background rings are defined in res/drawable/circle_progress_foreground.xml and res/drawable/circle_progress_background.xml, respectively. The drawables provide the developer the ability to modify the CIP size, color, ring thickness, etc., by tweaking the attributes. For example, the foreground circle looks like this:
 
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
    <item android:id="@android:id/progress">
            <shape
                android:innerRadius="@dimen/sixty_dp"
                android:shape="ring"
                android:thickness="@dimen/seven_dp">
                
                <gradient
                    android:startColor="@color/dark_blue"
                    android:endColor="@color/coral_blue"
                    android:type="sweep" />   
            </shape>
    </item>
</layer-list>
 
The tag is where the magic happens. A brief explanation of each attribute inside that tag:
 
// innerRadius defines the size of the inside of the ring in dp
android:innerRadius="@dimen/sixty_dp"
// shape defines the shape, in this case it’s a ring
android:shape="ring"
// thickness defines the thickness of the progress bar around the circle
android:thickness="@dimen/seven_dp">
// startColor is the beginning color of the ring
android:startColor="@color/dark_blue"
// endColor is the end color of the progress bar, regarless of length
android:endColor="@color/coral_blue"
// type is the type of gradient.  The operation system handles the transition of the sweep
android:type="sweep"
 
To add a CPI to your activity, you need to add two ProgressBar widgets to your activity’s layout.xml. Using a relative layout, place the ProgressBar widgets on top of each other:
 
<ProgressBar
	style="?android:attr/progressBarStyleHorizontal"
	android:layout_width="match_parent"
	android:layout_height="match_parent"
	android:layout_centerInParent="true"
	android:indeterminate="false"
	android:max="100"
	android:progress="100"	android:progressDrawable="@drawable/circle_progress_background" />

<ProgressBar
	android:id="@+id/circle_progress_bar"
	style="?android:attr/progressBarStyleHorizontal"
	android:layout_width="match_parent"
	android:layout_height="match_parent"
	android:layout_centerInParent="true"
	android:max="100"
	android:rotation="-90"
	android:indeterminate="false"
	android:progressDrawable="@drawable/circle_progress_foreground" />
 
These are standard ProgressBar widgets with nothing outside the ordinary (refer to the developer site for specifics on ProgressBar). The only customization we do here is using our new res drawable backgrounds on the ProgressBar widgets. The first one is set to background and the second is set to foreground. For the example app, we wanted progress to start at the 0 point on the circle, so the rotation tag is set to -90:
 
// first widget
android:progressDrawable="@drawable/circle_progress_background"

// second widget
android:progressDrawable="@drawable/circle_progress_foreground"
android:rotation="-90"
 
The foreground progress circle needs to be declared in your activity, and then you can set it with whatever value you’d like:
 
private ProgressBar mProgress;


mProgress.setProgress(65);
 
Happy coding!

Is this answer helpful?