It seems I cannot set ShapeDrawable as progressDrawable for Ratingbar. I tried the following but failed:
I customized the @ChrisJenkins method, so you can set the size of the RatingBar icons and custom tint colors for rating images. You can set padding on your RatingBar or set your custom width and height. @ChrisJenkins solution helped me a lot to finally come up with a totally customizable RatingBar. You can set the parameters in your XML also.
public class DrawableRatingBar extends RatingBar
{
// TileBitmap to base the width and hight off of.
@Nullable
private Bitmap iconTile;
private float scaleIconFactor;
private @ColorInt int iconBackgroundColor;
private @ColorInt int iconForegroundColor;
private @DrawableRes int iconDrawable;
public DrawableRatingBar(Context context)
{
super(context);
init();
}
public DrawableRatingBar(Context context, AttributeSet attrs)
{
super(context, attrs);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.DrawableRatingBarItem);
scaleIconFactor = a.getFloat(R.styleable.DrawableRatingBarItem_scaleIconFactor, 1);
iconBackgroundColor = a.getColor(R.styleable.DrawableRatingBarItem_iconBackgroundColor, Color.BLACK);
iconForegroundColor = a.getColor(R.styleable.DrawableRatingBarItem_iconForegroundColor, Color.WHITE);
iconDrawable = a.getResourceId(R.styleable.DrawableRatingBarItem_iconDrawable, -1);
a.recycle();
init();
}
public DrawableRatingBar(Context context, AttributeSet attrs, int defStyleAttr)
{
super(context, attrs, defStyleAttr);
init();
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public DrawableRatingBar(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)
{
super(context, attrs, defStyleAttr, defStyleRes);
init();
}
private void init()
{
setProgressDrawable(createProgressDrawable());
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (iconTile != null)
{
final int width = iconTile.getWidth() * getNumStars();
final int height = iconTile.getHeight();
setMeasuredDimension(resolveSizeAndState(width, widthMeasureSpec, 0),
resolveSizeAndState(height, heightMeasureSpec, 0));
}
}
protected LayerDrawable createProgressDrawable()
{
final Drawable backgroundDrawable = createBackgroundDrawableShape();
LayerDrawable layerDrawable = new LayerDrawable(new Drawable[] {backgroundDrawable,
backgroundDrawable,
createProgressDrawableShape()});
layerDrawable.setId(0, android.R.id.background);
layerDrawable.setId(1, android.R.id.secondaryProgress);
layerDrawable.setId(2, android.R.id.progress);
return layerDrawable;
}
protected Drawable createBackgroundDrawableShape()
{
Drawable drawable = ContextCompat.getDrawable(getContext(), iconDrawable);
final Bitmap tileBitmap = scaleImageWithColor(drawable, scaleIconFactor, iconBackgroundColor);
if (iconTile == null)
{
iconTile = tileBitmap;
}
final ShapeDrawable shapeDrawable = new ShapeDrawable(getDrawableShape());
final BitmapShader bitmapShader = new BitmapShader(tileBitmap, Shader.TileMode.REPEAT, Shader.TileMode.CLAMP);
shapeDrawable.getPaint().setShader(bitmapShader);
return shapeDrawable;
}
private void setRatingStarColor(Drawable drawable, @ColorInt int color)
{
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
{
DrawableCompat.setTint(drawable, color);
}
else
{
drawable.setColorFilter(color, PorterDuff.Mode.SRC_IN);
}
}
protected Drawable createProgressDrawableShape()
{
Drawable drawable = ContextCompat.getDrawable(getContext(), iconDrawable);
final Bitmap tileBitmap = scaleImageWithColor(drawable, scaleIconFactor, iconForegroundColor);
final ShapeDrawable shapeDrawable = new ShapeDrawable(getDrawableShape());
final BitmapShader bitmapShader = new BitmapShader(tileBitmap, Shader.TileMode.REPEAT, Shader.TileMode.CLAMP);
shapeDrawable.getPaint().setShader(bitmapShader);
return new ClipDrawable(shapeDrawable, Gravity.LEFT, ClipDrawable.HORIZONTAL);
}
Shape getDrawableShape()
{
return new RectShape();
}
public Bitmap scaleImageWithColor(Drawable drawable, float scaleFactor, @ColorInt int color)
{
Bitmap b = ((BitmapDrawable) drawable).getBitmap();
int sizeX = Math.round(drawable.getIntrinsicWidth() * scaleFactor);
int sizeY = Math.round(drawable.getIntrinsicHeight() * scaleFactor);
Bitmap bitmapResized = Bitmap.createScaledBitmap(b, sizeX, sizeY, true);
Canvas canvas = new Canvas(bitmapResized);
Paint paint = new Paint(Paint.FILTER_BITMAP_FLAG);
paint.setAntiAlias(true);
ColorFilter filter = new LightingColorFilter(color, 1);
paint.setColorFilter(filter);
canvas.drawBitmap(bitmapResized, 0, 0, paint);
return bitmapResized;
}
protected Bitmap fromDrawable(final Drawable drawable, final int height, final int width) {
final Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
final Canvas canvas = new Canvas(bitmap);
drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
drawable.draw(canvas);
return bitmap;
}
public float getScaleIconFactor() {
return scaleIconFactor;
}
public void setScaleIconFactor(float scaleIconFactor) {
this.scaleIconFactor = scaleIconFactor;
}
public int getIconForegroundColor() {
return iconForegroundColor;
}
public void setIconForegroundColor(int iconForegroundColor) {
this.iconForegroundColor = iconForegroundColor;
}
public int getIconBackgroundColor() {
return iconBackgroundColor;
}
public void setIconBackgroundColor(int iconBackgroundColor) {
this.iconBackgroundColor = iconBackgroundColor;
}
public int getIconDrawable() {
return iconDrawable;
}
public void setIconDrawable(int iconDrawable) {
this.iconDrawable = iconDrawable;
}
}
Custom attributes:
<declare-styleable name="DrawableRatingBarItem">
<attr name="scaleIconFactor" format="float"/>
<attr name="iconBackgroundColor" format="color"/>
<attr name="iconForegroundColor" format="color"/>
<attr name="iconDrawable" format="reference"/>
</declare-styleable>
You can find a step by step ratingBar styling here
try to set <item name="android:progressDrawable">
to an image instead of of shape, if it works that means your shape is causing the problem
Shape XML Drawables + RatingBar a complete mess.
This is as close as I could get to a solution without writing a whole new class.
My extended class builds the progress drawable correctly for the ProgressBar
clamping it as required.
Replace your empty and full states with the ones I've prepopulated. Not very flexible at the moment, you could easily abstract the setting of empty/full star states.
/**
* Created by chris on 28/08/2014.
* For Yoyo-Android.
*/
public class ShapeDrawableRatingBar extends RatingBar {
/**
* TileBitmap to base the width off of.
*/
@Nullable
private Bitmap mSampleTile;
public ShapeDrawableRatingBar(final Context context, final AttributeSet attrs) {
super(context, attrs);
setProgressDrawable(createProgressDrawable());
}
@Override
protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (mSampleTile != null) {
final int width = mSampleTile.getWidth() * getNumStars();
setMeasuredDimension(resolveSizeAndState(width, widthMeasureSpec, 0),
getMeasuredHeight());
}
}
protected LayerDrawable createProgressDrawable() {
final Drawable backgroundDrawable = createBackgroundDrawableShape();
LayerDrawable layerDrawable = new LayerDrawable(new Drawable[]{
backgroundDrawable,
backgroundDrawable,
createProgressDrawableShape()
});
layerDrawable.setId(0, android.R.id.background);
layerDrawable.setId(1, android.R.id.secondaryProgress);
layerDrawable.setId(2, android.R.id.progress);
return layerDrawable;
}
protected Drawable createBackgroundDrawableShape() {
final Bitmap tileBitmap = drawableToBitmap(getResources().getDrawable(R.drawable.ic_stamp_circle_empty));
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);
return shapeDrawable;
}
protected Drawable createProgressDrawableShape() {
final Bitmap tileBitmap = drawableToBitmap(getResources().getDrawable(R.drawable.ic_stamp_circle_full));
final ShapeDrawable shapeDrawable = new ShapeDrawable(getDrawableShape());
final BitmapShader bitmapShader = new BitmapShader(tileBitmap, Shader.TileMode.REPEAT, Shader.TileMode.CLAMP);
shapeDrawable.getPaint().setShader(bitmapShader);
return new ClipDrawable(shapeDrawable, Gravity.LEFT, ClipDrawable.HORIZONTAL);
}
Shape getDrawableShape() {
final float[] roundedCorners = new float[]{5, 5, 5, 5, 5, 5, 5, 5};
return new RoundRectShape(roundedCorners, null, null);
}
public static Bitmap drawableToBitmap(Drawable drawable) {
if (drawable instanceof BitmapDrawable) {
return ((BitmapDrawable) drawable).getBitmap();
}
int width = drawable.getIntrinsicWidth();
width = width > 0 ? width : 1;
int height = drawable.getIntrinsicHeight();
height = height > 0 ? height : 1;
final Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
final Canvas canvas = new Canvas(bitmap);
drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
drawable.draw(canvas);
return bitmap;
}
}
Calling setMax
setMaxStars
calls requestLayout
so you it will measure the width correctly. No need to work out the android:minWidth
, Just set android:layout_width="wrap_content"
.
Just remember you will need to add a bit of padding to your ShapeDrawables
as they get repeated edge to edge.
I dig RatingBar(and its parent class ProgressBar) source code, and found that ShapeDrawable is missing from tileify() which is called before setting progressDrawable in ProgressBar constructor:
private Drawable tileify(Drawable drawable, boolean clip) {
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 StateListDrawable) {
StateListDrawable in = (StateListDrawable) drawable;
StateListDrawable out = new StateListDrawable();
int numStates = in.getStateCount();
for (int i = 0; i < numStates; i++) {
out.addState(in.getStateSet(i), tileify(in.getStateDrawable(i), clip));
}
return out;
} else if (drawable instanceof BitmapDrawable) {
final Bitmap tileBitmap = ((BitmapDrawable) drawable).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);
return (clip) ? new ClipDrawable(shapeDrawable, Gravity.LEFT,
ClipDrawable.HORIZONTAL) : shapeDrawable;
}
return drawable;
}
I don't know why ShapeDrawable was not put there but I am considering to add it in subclassing code.