Standard Android SDK equipment provides developers with a wide range of components that can be used in application development. It is difficult to say how complete this set is and whether it meets all developer needs, however the platform itself is quite actively develops so we can expect that this set will be improved and expanded very often. And of course if something is missed in this set, you can always do it yourself :)

Among all this diversity you can find ProgressBar. This component is widely used to visualize the progress of a certain process or any other data. It needs almost everywhere. The component itself is quite simple and easy to use, but has a few nuances. For example it does not allows you to show some text on top of it. In case you need to do something like this:

there is no way to do it with default implementation. It seems to be okay, because you can always show the text somewhere on the bottom / top / left / right. But Android is a mobile platform, which means you have to fight for each pixel :) . Well, let’s try to solve this problem.

There are at least 3 ways that comes to mind immediately:

  • Built this component using existing ProgressBar and TextView
  • Extend TextView class and add progress indicator as a background
  • Extend ProgressBar and add text on top of it

The first option seems simple enough, but as the result you’ll have to work with three objects (TextView, ProgressBar, and container that combines them all in one) instead of one. Perhaps this is not something critical, but not awesome because final implementation seems a bit unwieldy for such a small task. Choosing between the second and third versions the last one seems most logical. After all, the main goal is to display an indicator whether the text is just a small supplement keeping in mind that you can actually live without it :) . Although it is possible that the second option would be more acceptable for someone.

OK, let’s create a new TextProgressBar class extended from ProgressBar and add extra property to display text.


package com.wvr.widget;

import android.content.Context;
import android.util.AttributeSet;
import android.widget.ProgressBar;

public class TextProgressBar extends ProgressBar {

private String text = "";
private int textColor = Color.BLACK;
private float textSize = 15;

public TextProgressBar(Context context) {
    super(context);
}

public TextProgressBar(Context context, AttributeSet attrs) {
    super(context, attrs);
}

public TextProgressBar(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
}

public String getText() {
    return text;
}

public synchronized void setText(String text) {
    this.text = text;
}

public int getTextColor() {
    return textColor;
}

public synchronized void setTextColor(int textColor) {
    this.textColor = textColor;
}

public float getTextSize() {
    return textSize;
}

public synchronized void setTextSize(float textSize) {
    this.textSize = textSize;
}

}

Our skeleton is ready now and we already can create an instance of this class and set up some text, text size and color. Now we will work directly with the problem. In order to display some text we will use onDraw() method and Paint class.


package com.wvr.widget;

import com.wvr.example.R;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.widget.ProgressBar;

public class TextProgressBar extends ProgressBar {

private String text = "";
private int textColor = Color.BLACK;
private float textSize = 15;

public TextProgressBar(Context context) {
    super(context);
}

public TextProgressBar(Context context, AttributeSet attrs) {
    super(context, attrs);
}

public TextProgressBar(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
}

@Override
protected synchronized void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    //create an instance of class Paint, set color and font size
    Paint textPaint = new Paint();
    textPaint.setAntiAlias(true);
    textPaint.setColor(textColor);
    textPaint.setTextSize(textSize);
    //In order to show text in a middle, we need to know its size
    Rect bounds = new Rect();
    textPaint.getTextBounds(text, 0, text.length(), bounds);
    //Now we store font size in bounds variable and can calculate it's position
    int x = getWidth() / 2 - bounds.centerX();
    int y = getHeight() / 2 - bounds.centerY();
    //drawing text with appropriate color and size in the center
    canvas.drawText(text, x, y, textPaint);
}

public String getText() {
    return text;
}

public synchronized void setText(String text) {
    if (text != null) {
        this.text = text;
    } else {
        this.text = "";
    }
    postInvalidate();
}

public int getTextColor() {
    return textColor;
}

public synchronized void setTextColor(int textColor) {
    this.textColor = textColor;
    postInvalidate();
}

public float getTextSize() {
    return textSize;
}

public synchronized void setTextSize(float textSize) {
    this.textSize = textSize;
    postInvalidate();
}

}

A few words about postInvalidate() method. Each time when we assign something to our variable (text, textColor or textSize) we must tell the system that our component has been modified and needs to be redrawn. To do this we use method postInvalidate().

TextProgressBar is ready now.


public class ExampleActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);
    TextProgressBar textProgressBar = (TextProgressBar) findViewById(R.id.progressBarWithText);
    textProgressBar.setText("Loading 70%");
    textProgressBar.setProgress(70);
    textProgressBar.setTextSize(18);
}

}

Layout.xml should look like this


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="@android:color/white"
android:orientation="vertical"
android:padding="10dp" >

<com.wvr.widget.TextProgressBar
   android:id="@+id/progressBarWithText"
   style="@android:style/Widget.ProgressBar.Horizontal"
   android:layout_width="match_parent"
   android:layout_height="wrap_content"
   android:max="100"
   android:maxHeight="30dp"
   android:minHeight="30dp"/>

</LinearLayout>

At this point you may say “done”. Generally speaking you are right, but we still have one question. Why we cannot set text and other properties in xml directly like minHeight and layout_width? Let’s add this options

To do this we create another file /res/values/attrs.xml with the following structure:


<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="TextProgressBar">
    <attr name="text" format="string" />
    <attr name="textColor" format="color" />
    <attr name="textSize" format="dimension"></attr>
</declare-styleable>
</resources>
  • name – property name. This is what we will use in XML
  • format – property type.You can read more here

Lets update our main.xml with this properties


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:components="http://schemas.android.com/apk/res/com.wvr.widget"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="@android:color/white"
android:orientation="vertical"
android:padding="10dp" >

<TextView
   android:layout_width="fill_parent"
   android:layout_height="wrap_content"
   android:text="@string/hello"
   android:textColor="@android:color/black" />

<com.wvr.widget.TextProgressBar
   android:id="@+id/progressBarWithText"
   style="@android:style/Widget.ProgressBar.Horizontal"
   android:layout_width="match_parent"
   android:layout_height="wrap_content"
   android:max="100"
   android:maxHeight="30dp"
   android:minHeight="30dp"
   android:progress="70"
   components:textSize="18dp"
   components:textColor="@android:color/black"
   components:text="Loading 70%" />

</LinearLayout>

Now we upgrade our TextProgressBar and add setAttrs method in which we read xml attributes


private void setAttrs(AttributeSet attrs) {
    if (attrs != null) {
        TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.TextProgressBar, 0, 0);
        setText(a.getString(R.styleable.TextProgressBar_text));
        setTextColor(a.getColor(R.styleable.TextProgressBar_textColor, Color.BLACK));
        setTextSize(a.getDimension(R.styleable.TextProgressBar_textSize, 15));
        a.recycle();
    }
}

As the result our TextProgressBar class should look like this:


package com.wvr.widget;

import com.wvr.example.R;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.widget.ProgressBar;

public class TextProgressBar extends ProgressBar {

private String text = "";
private int textColor = Color.BLACK;
private float textSize = 15;

public TextProgressBar(Context context) {
    super(context);
}

public TextProgressBar(Context context, AttributeSet attrs) {
    super(context, attrs);
    setAttrs(attrs);
}

public TextProgressBar(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
    setAttrs(attrs);
}

private void setAttrs(AttributeSet attrs) {
    if (attrs != null) {
        TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.TextProgressBar, 0, 0);
        setText(a.getString(R.styleable.TextProgressBar_text));
        setTextColor(a.getColor(R.styleable.TextProgressBar_textColor, Color.BLACK));
        setTextSize(a.getDimension(R.styleable.TextProgressBar_textSize, 15));
        a.recycle();
    }
}

@Override
protected synchronized void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    Paint textPaint = new Paint();
    textPaint.setAntiAlias(true);
    textPaint.setColor(textColor);
    textPaint.setTextSize(textSize);
    Rect bounds = new Rect();
    textPaint.getTextBounds(text, 0, text.length(), bounds);
    int x = getWidth() / 2 - bounds.centerX();
    int y = getHeight() / 2 - bounds.centerY();
    canvas.drawText(text, x, y, textPaint);
}

public String getText() {
    return text;
}

public synchronized void setText(String text) {
    if (text != null) {
        this.text = text;
    } else {
        this.text = "";
    }
    postInvalidate();
}

public int getTextColor() {
    return textColor;
}

public synchronized void setTextColor(int textColor) {
    this.textColor = textColor;
    postInvalidate();
}

public float getTextSize() {
    return textSize;
}

public synchronized void setTextSize(float textSize) {
    this.textSize = textSize;
    postInvalidate();
}
}

You should see the following picture now:

The last thing I’d like to cover here is branding. At the screenshot above you can see default ProgressBar look and feel. Lets do couple improvements to make it looks nice. To do this we will create another file /res/drawable/progressbar.xml with the following structure:


<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
<item android:id="@android:id/background">
    <shape>
        <corners android:radius="5dip" />
        <gradient
           android:angle="270"
           android:centerColor="#e7e5e5"
           android:centerY="0.75"
           android:endColor="#cfcdcd"
           android:startColor="#eceaea" />
    </shape>
</item>
<item android:id="@android:id/secondaryProgress">
    <clip>
        <shape>
            <corners android:radius="5dip" />
            <gradient
               android:angle="270"
               android:centerColor="#80ffb600"
               android:centerY="0.75"
               android:endColor="#a0ffcb00"
               android:startColor="#80ffd300" />
        </shape>
    </clip>
</item>
<item android:id="@android:id/progress">
    <clip>
        <shape>
            <corners android:radius="5dip" />
            <gradient
               android:angle="270"
               android:centerColor="#53a2b9"
               android:centerY="0.75"
               android:endColor="#53a2b9"
               android:startColor="#53a2b9" />
        </shape>
    </clip>
</item>
</layer-list>

As you see we can set corner radius. Also, using gradient you can set background color to both parts. Let’s now apply this style using progressDrawable property


<com.wvr.widget.TextProgressBar
   android:id="@+id/progressBarWithText"
   style="@android:style/Widget.ProgressBar.Horizontal"
   android:layout_width="match_parent"
   android:layout_height="wrap_content"
   android:max="100"
   android:maxHeight="30dp"
   android:minHeight="30dp"
   android:progress="70"
   android:progressDrawable="@drawable/progressbar"
   components:textSize="18dp"
   components:textColor="@android:color/black"
   components:text="Loading 70%" />

Thats it. If you have any ideas or suggestion – please let us know.