25

RatingBar in android is frustrating.

I've followed https://stackoverflow.com/a/13107765 to implement a custom ratingbar with my own drawables, but all the stars just superimpose each other, so that only a single star is finally displayed.

I can't, for the life of me, figure out why.

EDIT: PNG drawables seem to work fine, however SVGs used as vector drawables are causing this problem

Community
  • 1
  • 1
Ishaan Garg
  • 2,819
  • 3
  • 22
  • 27
  • I was using SVGs as vector drawables for the empty & filled stars, but then when I tried PNG drawables, it worked fine. There's some problem with the way Studio is handling SVGs then. – Ishaan Garg Nov 30 '15 at 15:58
  • 2
    This is a bug, here's issue: https://code.google.com/p/android/issues/detail?id=196713&q=vector%20ratingbar&colspec=ID%20Status%20Priority%20Owner%20Summary%20Stars%20Reporter%20Opened – Ivan Fork Dec 11 '15 at 12:19
  • Were you able to successfully use @harrane's answer below? – Forrest Bice Aug 10 '16 at 01:17
  • Didn't give it a try, just used PNGs instead – Ishaan Garg Aug 10 '16 at 06:40
  • 1
    @IvanFork The bug is now closed with "WorkingAsIntended"... I don't think, this will be fixed any time soon. Sadly. – AlbAtNf Aug 15 '16 at 11:11

1 Answers1

6

Here is a temp fix until they look into it in the support lib ( I've also put it in gist https://gist.github.com/harrane/815e8a94d7ca75d964714228af69c60c )

public class RatingBarVectorFix extends RatingBar {

    private Bitmap mSampleTile;

    public RatingBarVectorFix(Context context) {
        this(context, null);
    }

    public RatingBarVectorFix(Context context, AttributeSet attrs) {
        this(context, attrs, android.support.v7.appcompat.R.attr.ratingBarStyle);
    }

    public RatingBarVectorFix(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
        LayerDrawable drawable = (LayerDrawable) tileify(getProgressDrawable(), false);
        drawable.findDrawableByLayerId(android.R.id.background).setColorFilter(backgroundTintColor, PorterDuff.Mode.SRC_ATOP);
        drawable.findDrawableByLayerId(android.R.id.progress).setColorFilter(progressTintColor, PorterDuff.Mode.SRC_ATOP);
        setProgressDrawable(drawable);
    }

    /**
     * Converts a drawable to a tiled version of itself. It will recursively
     * traverse layer and state list drawables.
     */
    private Drawable tileify(Drawable drawable, boolean clip) {
        if (drawable instanceof DrawableWrapper) {
            Drawable inner = ((DrawableWrapper) drawable).getWrappedDrawable();
            if (inner != null) {
                inner = tileify(inner, clip);
                ((DrawableWrapper) drawable).setWrappedDrawable(inner);
            }
        } else if (drawable instanceof LayerDrawable) {
            LayerDrawable background = (LayerDrawable) drawable;
            final int N = background.getNumberOfLayers();
            Drawable[] outDrawables = new Drawable[N];

            for (int i = 0; i < N; i++) {
                int id = background.getId(i);
                outDrawables[i] = tileify(background.getDrawable(i),
                        (id == android.R.id.progress || id == android.R.id.secondaryProgress));
            }
            LayerDrawable newBg = new LayerDrawable(outDrawables);

            for (int i = 0; i < N; i++) {
                newBg.setId(i, background.getId(i));
            }

            return newBg;

        } else if (drawable instanceof BitmapDrawable) {
            final BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable;
            final Bitmap tileBitmap = bitmapDrawable.getBitmap();
            if (mSampleTile == null) {
                mSampleTile = tileBitmap;
            }

            final ShapeDrawable shapeDrawable = new ShapeDrawable(getDrawableShape());
            final BitmapShader bitmapShader = new BitmapShader(tileBitmap,
                    Shader.TileMode.REPEAT, Shader.TileMode.CLAMP);
            shapeDrawable.getPaint().setShader(bitmapShader);
            shapeDrawable.getPaint().setColorFilter(bitmapDrawable.getPaint().getColorFilter());
            return (clip) ? new ClipDrawable(shapeDrawable, Gravity.LEFT,
                    ClipDrawable.HORIZONTAL) : shapeDrawable;
        } else {
            return tileify(getBitmapDrawableFromVectorDrawable(drawable), clip);
        }

        return drawable;
    }

    private BitmapDrawable getBitmapDrawableFromVectorDrawable(Drawable drawable) {
        Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(bitmap);
        drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
        drawable.draw(canvas);
        return new BitmapDrawable(getResources(), bitmap);
    }

    @Override
    protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        if (mSampleTile != null) {
            final int width = mSampleTile.getWidth() * getNumStars();
            setMeasuredDimension(ViewCompat.resolveSizeAndState(width, widthMeasureSpec, 0),
                    getMeasuredHeight());
        }
    }

    private Shape getDrawableShape() {
        final float[] roundedCorners = new float[]{5, 5, 5, 5, 5, 5, 5, 5};
        return new RoundRectShape(roundedCorners, null, null);
    }
}
harrane
  • 939
  • 11
  • 24
  • 3
    This isn't rendering correctly for me. Are you providing your own drawables or letting the system handle that? An example of your use of this class in your layout file would be helpful. Additionally, is there an easy way to control the size of the star drawables if you let the system use it's built in star drawables? – Forrest Bice Aug 10 '16 at 01:15
  • It works but I can't set step size like 0.1. e.g. the half of a star is minimum when I want to show 0.3 – Yazon2006 Mar 26 '18 at 12:41
  • Here's the bug report for vectors not working with RatingBar views: issuetracker.google.com/issues/37074370 It was resolved as "Intended Behaviour", so the only officially supported option is using rendered images – oggmonster Apr 09 '18 at 10:11
  • Thanks! Only your solution works for SVG. I added an answer here: https://stackoverflow.com/a/53589663/2914140. – CoolMind Dec 03 '18 at 08:02