Tech tutorials Android Circle Progress Indicator
By Insight Editor / 25 Nov 2014 , Updated on 16 May 2019 / Topics: Application development
By Insight Editor / 25 Nov 2014 , Updated on 16 May 2019 / Topics: Application development
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.
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:
Link: https://github.com/CardinalNow/Android-CircleProgressIndicator
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”);
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!