11

I have a form which I'm dynamically generating from data I receive from a web service. This web service provides images which need to be used in the creation of input elements. I'm having difficuly in setting the progressDrawable of a RatingBar. Though XML I'm able to apply a custom image using the following as the progressDrawable:

<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:id="@+android:id/background" android:drawable="@drawable/custom_star" />
    <item android:id="@+android:id/secondaryProgress" android:drawable="@drawable/custom_star" />
    <item android:id="@+android:id/progress" android:drawable="@drawable/custom_star" />
</layer-list>

where custom_star is a simple .png image, and with @android:style/Widget.RatingBar as the RatingBar style. This works fine:

Working start

but I'm wanting to change custom_star dynamically.

In code, I have tried setting the progress drawable using a bitmap directly:

Drawable d = new BitmapDrawable(getResources(), downloadedImage);
ratingBar.setProgressDrawable(d);

and also by constructing a layer-list:

LayerDrawable layerDrawable = new LayerDrawable(new Drawable[] {
    getResources().getDrawable(R.drawable.custom_star),
    getResources().getDrawable(R.drawable.custom_star),             
    getResources().getDrawable(R.drawable.custom_star)
});

layerDrawable.setId(0, android.R.id.background);
layerDrawable.setId(1, android.R.id.secondaryProgress);
layerDrawable.setId(2, android.R.id.progress);

ratingBar.setProgressDrawable(layerDrawable);

Neither works for me; both result in the custom_star drawable appearing once, stretched by the dimensions of the RatingBar:

enter image description here

Any ideas?

Update:

Luksprog's answer below has made an improvement, but I'm still having a couple of issues. Now, the star drawable is not stretched and the value can be set by touch, but it appears as so with 3/5 selected:

no secondary

and 5/5 selected:

too many stars

I believe the scaling of the images can be fixed with a few tweaks, but annoyingly the secondaryProgress drawable doesn't seem to be set - the drawable used for the greyed out not-selected stars. Without that, it's not very usable.

blork
  • 2,150
  • 6
  • 26
  • 45

3 Answers3

12

Any ideas?

When using the default progressDrawable or a progressDrawable set through a theme all will be ok as in the constructor for the RatingBar(its superclass ProgressBar to be more precise) widget a method will be called to "make tiles" from that drawable. When using the setProgressDrawable method this doesn't happen and if you pass a simple BitmapDrawable or a LayerDrawable(with simple BitmapDrawables) that Drawable will simply be stretched to cover the widget's background area(what you see know).

In order to make it work you would need to manually do what the RatingBar does at start, create the tiles along with the ClipDrawables that it uses. I've written a method for this, following the source code of the ProgressBar widget:

private Drawable buildRatingBarDrawables(Bitmap[] images) {
    final int[] requiredIds = { android.R.id.background,
            android.R.id.secondaryProgress, android.R.id.progress };
    final float[] roundedCorners = new float[] { 5, 5, 5, 5, 5, 5, 5, 5 };
    Drawable[] pieces = new Drawable[3];
    for (int i = 0; i < 3; i++) {
        ShapeDrawable sd = new ShapeDrawable(new RoundRectShape(
                roundedCorners, null, null));
        BitmapShader bitmapShader = new BitmapShader(images[i],
                Shader.TileMode.REPEAT, Shader.TileMode.CLAMP);
        sd.getPaint().setShader(bitmapShader);
        ClipDrawable cd = new ClipDrawable(sd, Gravity.LEFT,
                ClipDrawable.HORIZONTAL);
        if (i == 0) {
            pieces[i] = sd;
        } else {
            pieces[i] = cd;
        }
    }
    LayerDrawable ld = new LayerDrawable(pieces);
    for (int i = 0; i < 3; i++) {
        ld.setId(i, requiredIds[i]);
    }
    return ld;
}

Then you would use the LayerDrawable returned by this method with the setProgressDrawable method. The RatingBar set its width multiplying the width of one of the state bitmaps with the number of stars, so in order to show the right amount of stars this has to be calculated as well.

user
  • 86,916
  • 18
  • 197
  • 190
  • Great stuff. This is definitely working for me, although I can't seem to get it to set secondaryProgress. The stars appear when clicked, but the grayed-out unselected background ones don't appear (I'm using a different drawable, R.drawable.star_off). – blork Feb 26 '13 at 16:24
  • 1
    While your exact solution didn't work for me, I checked out the [Android source code for ProgressBar](http://grepcode.com/file_/repository.grepcode.com/java/ext/com.google.android/android/2.3.3_r1/android/widget/ProgressBar.java/) as you suggested, and ripping out the tileify() method worked great for me. Thanks! – blork Feb 28 '13 at 10:09
  • 2
    @blork Sorry about the code I posted(didn't had much time), it didn't fully replicate that method(I somehow lost that `clip` flag). I'm sure you managed to also make the stars have the correct size. The `RatingBar` sets its width by multiplying the width of one of the bitmaps with the number of stars(calculating this in the `onMeasure` method of a custom `RatingBar` should show the right amount of stars). – user Feb 28 '13 at 10:39
  • Fan-freaking-tastic. Been trying to work out a fix for this for days. This is very well written. Just remember that you MUST pass 3 bitmaps else you will get an outofbounds exception. Also, for those who are trying to use Drawables and realize you can't pass them, just convert using this method: http://stackoverflow.com/questions/8717333/converting-drawable-resource-image-into-bitmap – PGMacDesign Sep 29 '15 at 19:42
0

Your ids are wrong, you have to set @android:id/background @android:id/secondaryProgress @android:id/progress

You can also define a drawable in xml containing your customization

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:id="@+android:id/background"
          android:drawable="@drawable/star_empty" />
    <item android:id="@+android:id/secondaryProgress"
          android:drawable="@drawable/star_empty" />
    <item android:id="@+android:id/progress"
          android:drawable="@drawable/star_full" />
</layer-list>

And you can use setMax on the RatingBar object to set the maximum amount of stars that can be displayed

Fernando Gallego
  • 4,064
  • 31
  • 50
0

It's much easier with such solution:

RatingBar ratingBar = new RatingBar(new ContextThemeWrapper(context, R.style.AppTheme_RatingBar), null, 0);
Tsung Wu
  • 1,064
  • 9
  • 17