Developer Aspirations

YAPB - Yet Another Programming Blog

Thursday

07

October 2010

How to Add Text Over a Progress Bar on Android

by Colin Miller, on Android, development

As a minor reprieve from my usual meandering babble, here's a tidbit that's actually useful in a practical sense. Adding text on a progress bar on Android.

The ProgressBar class on android is a fickle thing. By default, it starts out in a state of a constantly moving circle indicating generic 'progress' being made on whatever task it is that you're doing. It's nice for any process where you have no clue how long the action will really take or how far along you are in it. However, sometimes you want a Progress Bar in the shape of an actual bar, indicating progress by filling up as a percentage of the progress happening. Or, as was the case with me, sometimes you just want a nice graphical way to display a character's hit points so the user can watch it go down.

Modifying a ProgressBar to become an actual bar isn't too difficult once you know how. All you have to do is modify the xml declaration of the Progress Bar in your layout xml as such:

<ProgressBar  
android:id="@+id/player_hp_bar"  
android:layout_width="fill_parent"  
android:layout_height="wrap_content"  
android:max="100"  
android:progress="0"  
style="?android:attr/progressBarStyleHorizontal"  
android:maxHeight="12dip"  
android:minHeight="12dip"  
/>
The key part to that is the style="?android:attr/progressBarStyleHorizontal". It's what sets the ProgressBar to actually look like a Bar. I also decided to use android:maxHeight and android:minHeight (you need both) to make the bar smaller so I could fit more of the things on my screen. After you've made your progress bar a bar, you're probably wondering how do I put text over top of that? Well, the default implementation of ProgressBar has no such functionality, which sort of sucks. After foraging around the internet for an hour or so, I rolled my own solution. It's not too complicated, and here it is. First we're going to create a subclass of ProgressBar. The subclass will work pretty much the same way, however it'll contain an additional property for holding a String to draw over top of the progress bar. I also decided to add in a color property in case I want to have different colored text on my new TextProgressBar.
  
public class TextProgressBar extends ProgressBar {  
    private String text;
    private Paint textPaint;

    public TextProgressBar(Context context) {
        super(context);
        text = "HP";
        textPaint = new Paint();
        textPaint.setColor(Color.BLACK);
    }

    public TextProgressBar(Context context, AttributeSet attrs) {
        super(context, attrs);
        text = "HP";
        textPaint = new Paint();
        textPaint.setColor(Color.BLACK);
    }

    public TextProgressBar(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        text = "HP";
        textPaint = new Paint();
        textPaint.setColor(Color.BLACK);
    }

    @Override
    protected synchronized void onDraw(Canvas canvas) {
        // First draw the regular progress bar, then custom draw our text
        super.onDraw(canvas);
        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 synchronized void setText(String text) {
        this.text = text;
        drawableStateChanged();
    }

    public void setTextColor(int color) {
        textPaint.setColor(color);
        drawableStateChanged();
    }
}
I'm actually not sure which constructor is used when creating a widget via an XML file, so I overrode them all. The important part is the @Override of the onDraw(Canvas) method. This lets us call the ProgressBar's onDraw method to actually draw the bar, but then take the results and draw some text over top of it. The Paint class not only supplies the color for the text, but also has the niffty ability of telling us how big the text would be to draw so we can line it up properly. For this class I have the text centered, but you could adjust it to change the orientation however you would like. In order to display this new bar we just have to change the XML entry:
  
<com.colintmiller.statgame.widgets.TextProgressBar
android:id="@+id/player_hp_bar"  
android:layout_width="fill_parent"  
android:layout_height="wrap_content"  
android:max="100"  
android:progress="0"  
style="?android:attr/progressBarStyleHorizontal"  
android:maxHeight="12dip"  
android:minHeight="12dip"  
/>
This just has us call the widget by the full name of the class including package. The rest stays the same. By default it will work just like a normal ProgressBar. However, if you want to display some text you just have to add it in your code:
  
    private void setHpBar(Actor a, TextProgressBar bar) {
        bar.setMax(a.getMaxHP());
        bar.setProgress(a.getCurrentHP());
        bar.setText(a.getCurrentHP() + "/" + a.getMaxHP());
    }

In this I've already gotten the TextProgressBar previously with findViewById(R.id.playerhpbar) and the Actor is a bean I keep player data in. The rest should be fairly self explanatory.

Bonus Tip

Another thing that I spent a lot of time with on with the ProgressBar was changing its color. This seems like it should be easy to do, but unfortunately it's not. Here's the step by step. First, copy the contents below into a file named green-progress.xml inside of your res/drawable directory (make one if you don't have it).

  
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">

<item android:id="@android:id/background">
    <shape>
        <corners android:radius="5dip" />
        <gradient
                android:startColor="#ff9d9e9d"
                android:centerColor="#ff5a5d5a"
                android:centerY="0.75"
                android:endColor="#ff747674"
                android:angle="270"
        />
    </shape>
</item>

<item android:id="@android:id/secondaryProgress">
    <clip>
        <shape>
            <corners android:radius="5dip" />
            <gradient
                    android:startColor="#80ffd300"
                    android:centerColor="#80ffb600"
                    android:centerY="0.75"
                    android:endColor="#a0ffcb00"
                    android:angle="270"
            />
        </shape>
    </clip>
</item>
<item
    android:id="@android:id/progress">
    <clip>
        <shape>
            <corners
                android:radius="5dip" />
            <gradient
                    android:startColor="@color/greenStart"
                    android:centerColor="@color/greenMid"
                    android:centerY="0.75"
                    android:endColor="@color/greenEnd"
                    android:angle="270"
            />
        </shape>
    </clip>
</item>

</layer-list>

You can modify the greenStart, greenMid, and greenEnd to be any colors you want. For the green ones here was my entries in the color.xml file:

  
<color name="greenStart">#ff33dd44</color>
<color name="greenMid">#ff0A8815</color>
<color name="greenEnd">#ff1da130</color>

Then you just need to set this as the drawable of your ProgressBar. The drawable name is the full name of the file minus the extension (so in this case, green-progess). Here's how I did it:

  
        expBar = (TextProgressBar) findViewById(R.id.player_exp_bar);
        expBar.setProgressDrawable(getResources().getDrawable(R.drawable.green_progress));

You need to get it to redraw to display correctly. You can do that by setting some text, changing the progress value, or just calling the invalidate() method on it.

So there, now you can add text to a ProgressBar and change its color. Hopefully I've saved someone the few hours that I wasted trying to figure out how to do that. Here's an example of both practices in use:
[caption id="attachment_317" align="aligncenter" width="480" caption="This is a pre-alpha version of the game I\'m making for PoV\'s challenge. Mainly I\'m displaying it to show of the multi-colored ProgressBars with text over top of them."][/caption]

comments powered by Disqus