Hello all i need to perform Ratings in my application... SO i need to create custom Ratings bar... Can Anyone Help me in this?
Making the custom rating bar with layer list and selectors is complex, it is better to override the RatingBar class and create a custom RatingBar. createBackgroundDrawableShape() is the function where you should put your empty state png and createProgressDrawableShape() is the function where you should put your filled state png.
Note: This code will not work with svg for now.
public class CustomRatingBar extends RatingBar {
@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_star_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_star_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;
}
}
I investigated the original source,
and here is my result.
styles.xml (res/values)
<!-- RatingBar -->
<style name="RatingBar" parent="@android:style/Widget.RatingBar">
<item name="android:progressDrawable">@drawable/ratingbar_full</item>
<item name="android:indeterminateDrawable">@drawable/ratingbar_full</item>
<item name="android:minHeight">13.4dp</item>
<item name="android:maxHeight">13.4dp</item>
</style>
ratingbar_full.xml (res/drawable)
<?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/btn_rating_star_off_normal" />
<item android:id="@android:id/secondaryProgress" android:drawable="@drawable/btn_rating_star_off_normal" />
<item android:id="@android:id/progress" android:drawable="@drawable/btn_rating_star_on_normal" />
</layer-list>
btn_rating_star_off_normal.png (res/drawable-xxhdpi)
btn_rating_star_on_normal.png (res/drawable-xxhdpi)
activity_ratingbar.xml (res/layout)
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.appcompat.widget.AppCompatRatingBar
android:id="@+id/ratingbar"
style="@style/RatingBar"
android:layout_width="wrap_content"
android:layout_height="13.4dp"
android:isIndicator="false"
android:numStars="5"
android:rating="2.6"
android:secondaryProgressTint="#00000000"
android:stepSize="0.1" />
</FrameLayout>
This is the result.
layout_height
property, because if it is wrap_content
it will draw lines below stars. (in my case only in a preview of Android Studio)first add images to drawable:
the first picture "ratingbar_staroff.png" and the second "ratingbar_staron.png"
After, create "ratingbar.xml" on res/drawable
<?xml version="1.0" encoding="utf-8"?>
<!--suppress AndroidDomInspection -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@+android:id/background"
android:drawable="@drawable/ratingbar_empty" />
<item android:id="@+android:id/secondaryProgress"
android:drawable="@drawable/ratingbar_empty" />
<item android:id="@+android:id/progress"
android:drawable="@drawable/ratingbar_filled" />
</layer-list>
the next xml the same on res/drawable
"ratingbar_empty.xml"
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true"
android:state_window_focused="true"
android:drawable="@drawable/ratingbar_staroff" />
<item android:state_focused="true"
android:state_window_focused="true"
android:drawable="@drawable/ratingbar_staroff" />
<item android:state_selected="true"
android:state_window_focused="true"
android:drawable="@drawable/ratingbar_staroff" />
<item android:drawable="@drawable/ratingbar_staroff" />
</selector>
"ratingbar_filled"
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true"
android:state_window_focused="true"
android:drawable="@drawable/ratingbar_staron" />
<item android:state_focused="true"
android:state_window_focused="true"
android:drawable="@drawable/ratingbar_staron" />
<item android:state_selected="true"
android:state_window_focused="true"
android:drawable="@drawable/ratingbar_staron" />
<item android:drawable="@drawable/ratingbar_staron" />
</selector>
the next to do, add these lines of code on res/values/styles
<style name="CustomRatingBar" parent="@android:style/Widget.RatingBar">
<item name="android:progressDrawable">@drawable/ratingbar</item>
<item name="android:minHeight">18dp</item>
<item name="android:maxHeight">18dp</item>
</style>
Now, already can add style to ratingbar resource
<RatingBar
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style= "@style/CustomRatingBar"
android:id="@+id/ratingBar"
android:numStars="5"
android:stepSize="0.01"
android:isIndicator="true"/>
finally on your activity only is declare:
RatingBar ratingbar = (RatingBar) findViewById(R.id.ratingbar);
ratingbar.setRating(3.67f);
You can use @erdomester 's given solution for this. But if you are facing issues with rating bar height then you can use ratingbar's icons height programmatically.
In Kotlin,
val drawable = ContextCompat.getDrawable(context, R.drawable.rating_filled)
val drawableHeight = drawable.intrinsicHeight
rating_bar.layoutParams.height = drawableHeight
Edit
Have a look at custom rating in motorola http://community.developer.motorola.com/t5/Android-App-Development-for/custom-rating-bar-style-using-android-s-ratingBar-small-style/td-p/10462
Updated
styles.xml
This must be located in your values folder
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="foodRatingBar" parent="@android:style/Widget.RatingBar">
<item name="android:progressDrawable">@drawable/food_rating_bar_full</item>
<item name="android:minHeight">23dip</item>
<item name="android:maxHeight">25dip</item>
</style>
</resources>
food_rating_bar_full.xml
This file must be in Drawable folder.
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@+id/background"
android:drawable="@drawable/food_ratingbar_full_empty" />
<item android:id="@+id/secondaryProgress"
android:drawable="@drawable/food_ratingbar_full_empty" />
<item android:id="@+id/progress"
android:drawable="@drawable/food_ratingbar_full_filled" />
</layer-list>
food_ratingbar_full_empty.xml
This file must be inside Drawable folder.
<?xml version="1.0" encoding="utf-8"?>
<!-- This is the rating bar drawable that is used to
show a filled cookie. -->
<selector
xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true"
android:state_window_focused="true"
android:drawable="@drawable/cookiee" />
<item android:state_focused="true"
android:state_window_focused="true"
android:drawable="@drawable/cookiee" />
<item android:state_selected="true"
android:state_window_focused="true"
android:drawable="@drawable/cookiee" />
<item android:drawable="@drawable/cookiee" />
</selector>
food_ratingbar_full_filled.xml
This file must be located in Drawable folder.
<?xml version="1.0" encoding="utf-8"?>
<!-- This is the rating bar drawable that is used to
show a unfilled cookie. -->
<selector
xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true"
android:state_window_focused="true"
android:drawable="@drawable/cookie" />
<item android:state_focused="true"
android:state_window_focused="true"
android:drawable="@drawable/cookie" />
<item android:state_selected="true"
android:state_window_focused="true"
android:drawable="@drawable/cookie" />
<item android:drawable="@drawable/cookie" />
</selector>
main.xml file should look like :
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<RatingBar android:id="@+id/ratingBar1"
style="@style/foodRatingBar"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
</RatingBar>
</LinearLayout>
MainActivity.class should look like :
import android.app.Activity;
import android.os.Bundle;
import android.widget.RatingBar;
import android.widget.RatingBar.OnRatingBarChangeListener;
import android.widget.Toast;
public class MainActivity extends Activity {
/** Called when the activity is first created. */
RatingBar rb;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
rb=(RatingBar)findViewById(R.id.ratingBar1);
rb.setOnRatingBarChangeListener(new OnRatingBarChangeListener(){
@Override
public void onRatingChanged(RatingBar ratingBar, float rating,
boolean fromUser) {
// TODO Auto-generated method stub
Toast.makeText(getApplicationContext(),Float.toString(rating),Toast.LENGTH_LONG).show();
}
});
}
}
I have used two images:
cookie.jpg
cookiee.jpg
This two images are of same size one is used for identifying selected Rating Bar and other for identifying unselected RatingBar
When creating a custom rating bar that displays a solid gradient line running on a SeekBar-like track, rather than stars, I also encountered a problem related to the vertical centering of the background (track drawable). This is the flawed drawable code I used originally (which generated the problem), as suggested by Android developer and other StackOverflow entries:
<?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/seekbar_track"/>
<item android:id="@android:id/secondaryProgress">
<scale
android:drawable="@drawable/seekbar_progress2"
android:scaleWidth="100%" />
</item>
<item android:id="@android:id/progress" >
<clip android:clipOrientation="horizontal" android:gravity="left" >
<shape>
<gradient
android:startColor="@color/ratingbar_bg_start"
android:centerColor="@color/ratingbar_bg_center"
android:centerX="0.5"
android:endColor="@color/ratingbar_bg_end"
android:angle="0"
/>
</shape>
</clip>
</item>
</layer-list>
The problem here is the first item, which relates to the background of the custom RatingBar. Many entries will tell you to set the layout_minHeight feature to some large value to avoid a vertical spatial disconnect between the thumb and its track. This was not the solution for me - when viewed on a tablet, the background was still drawing to its smaller phone-based size - so the track was consistently positioned well above the center of the RatingBar track. The solution is to remove this entry in the RatingBar drawable, so it now looks like this:
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@android:id/secondaryProgress">
<scale
android:drawable="@drawable/seekbar_progress2"
android:scaleWidth="100%" />
</item>
<item android:id="@android:id/progress" >
<clip android:clipOrientation="horizontal" android:gravity="left" >
<shape>
<gradient
android:startColor="@color/ratingbar_bg_start"
android:centerColor="@color/ratingbar_bg_center"
android:centerX="0.5"
android:endColor="@color/ratingbar_bg_end"
android:angle="0"
/>
</shape>
</clip>
</item>
</layer-list>
Then, in the style definition of the custom RatingBar, set the layout_background to the the track drawable. Mine looks like this:
<style name="styleRatingBar" parent="@android:style/Widget.RatingBar">
<item name="android:indeterminateOnly">false</item>
<item name="android:background">@drawable/seekbar_track</item>
<item name="android:progressDrawable">@drawable/abratingbar</item>
<item name="android:thumb">@drawable/abseekbar_thumb</item>
<item name="android:minHeight">@dimen/base_29dp</item>
<item name="android:maxHeight">@dimen/base_29dp</item>
<item name="android:layout_marginLeft">@dimen/base_10dp</item>
<item name="android:layout_marginRight">@dimen/base_10dp</item>
<item name="android:layout_marginTop">@dimen/base_10dp</item>
<item name="android:layout_marginBottom">@dimen/base_10dp</item>
<item name="android:scaleType">fitXY</item>
</style>
(Previously, the background setting here was undefined.).
This is the entry in my layout, which uses both the style and the drawables:
<RatingBar
android:id="@+id/ratingbar_vote"
style="@style/styleRatingBar"
android:hint="@string/ratingbar_vote"
android:contentDescription="@string/ratingbar_vote"
android:numStars="5"
android:rating="5"
android:stepSize="1"
android:layout_width="match_parent"
android:layout_height="@dimen/base_29dp"
android:layout_marginLeft="@dimen/base_120dp"
android:layout_gravity="bottom|right" />
So, to summarize, do not set the background (track) feature in your custom RatingBar drawable, set it in the layout_background feature of your custom RatingBar style. This ensures the track is always vertically centered in a horizontal RatingBar. (Remember, in this custom RatingBar, instead of using stars or other isolated images as the rating, I'm using a gradient line that "grows" or "shrinks" horizontally to display the rating - this rating line uses a SeekBar-like thumb running on a SeekBar-like "track".)