Cómo crear una barra de calificaciones personalizadas en Android
80
Hola, todo lo que necesito para realizar calificaciones en mi aplicación ... ASÍ que necesito crear una barra de calificaciones personalizada ... ¿Alguien puede ayudarme en esto?
Algunos fragmentos de código serían útiles (tal vez maquetas de su objetivo), para ver dónde se encuentra hasta ahora. Mirando su publicación, no parece un problema a resolver, más una oferta de trabajo, por favor, explique eso.
Este archivo debe estar dentro de la carpeta Drawable.
<?xml version="1.0" encoding="utf-8"?><!-- This is the rating bar drawable that is used to
show a filled cookie. --><selectorxmlns:android="http://schemas.android.com/apk/res/android"><itemandroid:state_pressed="true"android:state_window_focused="true"android:drawable="@drawable/cookiee" /><itemandroid:state_focused="true"android:state_window_focused="true"android:drawable="@drawable/cookiee" /><itemandroid:state_selected="true"android:state_window_focused="true"android:drawable="@drawable/cookiee" /><itemandroid:drawable="@drawable/cookiee" /></selector>
food_ratingbar_full_losed.xml
Este archivo debe estar ubicado en la carpeta Drawable.
<?xml version="1.0" encoding="utf-8"?><!-- This is the rating bar drawable that is used to
show a unfilled cookie. --><selectorxmlns:android="http://schemas.android.com/apk/res/android"><itemandroid:state_pressed="true"android:state_window_focused="true"android:drawable="@drawable/cookie" /><itemandroid:state_focused="true"android:state_window_focused="true"android:drawable="@drawable/cookie" /><itemandroid:state_selected="true"android:state_window_focused="true"android:drawable="@drawable/cookie" /><itemandroid:drawable="@drawable/cookie" /></selector>
import android.app.Activity;
import android.os.Bundle;
import android.widget.RatingBar;
import android.widget.RatingBar.OnRatingBarChangeListener;
import android.widget.Toast;
publicclassMainActivityextendsActivity{
/** Called when the activity is first created. */
RatingBar rb;
@OverridepublicvoidonCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
rb=(RatingBar)findViewById(R.id.ratingBar1);
rb.setOnRatingBarChangeListener(newOnRatingBarChangeListener(){
@OverridepublicvoidonRatingChanged(RatingBar ratingBar, float rating,
boolean fromUser) {
// TODO Auto-generated method stub
Toast.makeText(getApplicationContext(),Float.toString(rating),Toast.LENGTH_LONG).show();
}
});
}
}
He usado dos imágenes:
cookie.jpg
cookiee.jpg
Estas dos imágenes son del mismo tamaño, una se utiliza para identificar la barra de clasificación seleccionada y la otra para identificar la barra de clasificación no seleccionada
Esto funcionó para mí, después de cambiar en el archivo food_rating_bar_full.xml, las identificaciones del artículo. Reemplacé <item android: id = "@ + android: id / background" con <item android: id = "@ android: id / background" (sin el símbolo +).
lo mismo que sugirió @erdomester pero más código. PRECAUCIÓN: ¡No olvide que no funciona con imágenes vectoriales, solo con .png!
Kirill Karmazin
1
No funcionó para mí, pero estuvo bien cuando reemplacé: - android: id = "@ + id / background con @android: id / background - android: id =" @ + id / secondaryProgress "con android: id = "@ android: id / secondaryProgress" - android: id = "@ + id / progress" con android: id = "@ android: id / progress"
Haris Dautović
56
Necesito agregar mi solución, que es MUCHO más fácil que la anterior. Ni siquiera necesitamos usar estilos.
+1, pero lo siento, esto debería estar funcionando, pero no lo está. Primero, dijiste que agregaste el Selector, pero de hecho agregaste una lista de capas. En segundo lugar, probé su código exactamente, pero todo lo que obtengo es un componente en blanco (sin imagen). Tal vez puedas responder mi pregunta: stackoverflow.com/questions/14251092/… Gracias: D
Blaze Tama
3
Este enfoque simple da como resultado problemas para colocar la barra de clasificación en un diseño. Un enfoque más tradicional para definir un estilo es mejor.
javaxian
3
muestra una línea debajo de todas las estrellas de la barra de clasificación
Domingo
3
Funciona muy bien, gracias. PRECAUCIÓN: ¡No olvide que no funciona con imágenes vectoriales, solo con .png!
Es similar a la respuesta marcada. Pero tiene imágenes (muestra estrellas fraccionarias) y <! - suprime AndroidDomInspection -> en la lista de capas. En su lugar, puede eliminar un signo más en "@ + android: id / background" para evitar errores de Android. También debería cambiar minHeight y maxHeight a 50dp.
CoolMind
Gracias por la solucion ¿Puedo saber cómo se ajusta el tamaño de las estrellas? los míos salen muy pequeños. gracias.
Jay
@jay en res/values/stylesél está definiendo android:minHeighty android:maxHeightcambiando eso para hacer que el tamaño de la estrella sea grande
Inzimam Tariq IT
7
Hacer la barra de clasificación personalizada con lista de capas y selectores es complejo, es mejor anular la clase RatingBar y crear una RatingBar personalizada. createBackgroundDrawableShape () es la función donde debe poner su estado vacío png y createProgressDrawableShape () es la función donde debe poner su estado relleno png.
Nota: este código no funcionará con svg por ahora.
publicclassCustomRatingBarextendsRatingBar{
@Nullableprivate Bitmap mSampleTile;
publicShapeDrawableRatingBar(final Context context, final AttributeSet attrs){
super(context, attrs);
setProgressDrawable(createProgressDrawable());
}
@OverrideprotectedsynchronizedvoidonMeasure(int widthMeasureSpec, int heightMeasureSpec){
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (mSampleTile != null) {
finalint 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);
returnnew ClipDrawable(shapeDrawable, Gravity.LEFT, ClipDrawable.HORIZONTAL);
}
Shape getDrawableShape(){
finalfloat[] roundedCorners = newfloat[]{5, 5, 5, 5, 5, 5, 5, 5};
returnnew RoundRectShape(roundedCorners, null, null);
}
publicstatic 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;
}
}
¿Cómo se puede aumentar el espacio entre elementos?
Ali Rezaiyan
1
@AliRezaiyan: Dado que estamos usando nuestros propios elementos de diseño, es decir, ic_star_full e ic_star_empty, simplemente puede agregar un espacio vacío (relleno) a los lados izquierdo y derecho de su PNG. Puede usar herramientas como GIMP o Android Asset Studio para lograr esto editando su PNG. Espero que esto ayude.
Gaurav Saluja
Estoy buscando una forma programática
Ali Rezaiyan
5
Para SVGRatingBar Solía encargo RatingBar vector dibujables superposición y la respuesta de erdomester aquí. Esta solución atraviesa todos los elementos SvgRatingBarde diseño dentro de la vista de su diseño, por RecyclerViewlo que tiene una sobrecarga.
SvgRatingBar.java:
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.Shader;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.ClipDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.graphics.drawable.ShapeDrawable;
import android.graphics.drawable.VectorDrawable;
import android.graphics.drawable.shapes.RoundRectShape;
import android.graphics.drawable.shapes.Shape;
import android.os.Build;
import android.util.AttributeSet;
import android.view.Gravity;
import androidx.appcompat.graphics.drawable.DrawableWrapper;
import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat;
import com.example.R; // Your R.java file for R.attr.ratingBarStyle.publicclassSvgRatingBarextendsandroidx.appcompat.widget.AppCompatRatingBar{
private Bitmap sampleTile;
publicSvgRatingBar(Context context){
this(context, null);
}
publicSvgRatingBar(Context context, AttributeSet attrs){
this(context, attrs, R.attr.ratingBarStyle);
}
publicSvgRatingBar(Context context, AttributeSet attrs, int defStyleAttr){
super(context, attrs, defStyleAttr);
init();
}
privatevoidinit(){
LayerDrawable drawable = (LayerDrawable) createTile(getProgressDrawable(), false);
setProgressDrawable(drawable);
}
/**
* Converts a drawable to a tiled version of itself. It will recursively
* traverse layer and state list drawables.
*/@SuppressLint("RestrictedApi")private Drawable createTile(Drawable drawable, boolean clip){
if (drawable instanceof DrawableWrapper) {
Drawable inner = ((DrawableWrapper) drawable).getWrappedDrawable();
if (inner != null) {
inner = createTile(inner, clip);
((DrawableWrapper) drawable).setWrappedDrawable(inner);
}
} elseif (drawable instanceof LayerDrawable) {
LayerDrawable background = (LayerDrawable) drawable;
finalint n = background.getNumberOfLayers();
Drawable[] outDrawables = new Drawable[n];
for (int i = 0; i < n; i++) {
int id = background.getId(i);
outDrawables[i] = createTile(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;
} elseif (drawable instanceof BitmapDrawable) {
final BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable;
final Bitmap tileBitmap = bitmapDrawable.getBitmap();
if (sampleTile == null) {
sampleTile = 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.START,
ClipDrawable.HORIZONTAL) : shapeDrawable;
} elseif (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && drawable instanceof VectorDrawable) {
return createTile(getBitmapDrawableFromVectorDrawable(drawable), clip);
} elseif (drawable instanceof VectorDrawableCompat) {
// API 19 support.return createTile(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);
returnnew BitmapDrawable(getResources(), bitmap);
}
@OverrideprotectedsynchronizedvoidonMeasure(int widthMeasureSpec, int heightMeasureSpec){
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (sampleTile != null) {
finalint width = sampleTile.getWidth() * getNumStars();
setMeasuredDimension(resolveSizeAndState(width, widthMeasureSpec, 0),
getMeasuredHeight());
}
}
private Shape getDrawableShape(){
finalfloat[] roundedCorners = newfloat[]{5, 5, 5, 5, 5, 5, 5, 5};
returnnew RoundRectShape(roundedCorners, null, null);
}
}
Tenga en cuenta que agregué la altura real (13.4dp) de la barra de calificación en la layout_heightpropiedad, porque si es wrap_contentasí, dibujará líneas debajo de las estrellas. (en mi caso solo en una vista previa de Android Studio)
Hice algo similar, una RatingBar con íconos de calificación individuales, estoy usando VectorDrawables para los íconos de calificación pero podrías usar cualquier tipo de elemento de diseño
Puede crear una barra de clasificación de material personalizada definiendo xml dibujable utilizando el icono de material de su elección y luego aplicando el elemento de diseño personalizado a la barra de clasificación utilizando el atributo progressDrawable.
Al crear una barra de clasificación personalizada que muestra una línea de degradado sólida que se ejecuta en una pista tipo SeekBar, en lugar de estrellas, también encontré un problema relacionado con el centrado vertical del fondo (pista dibujable). Este es el código de diseño defectuoso que usé originalmente (que generó el problema), como lo sugirió el desarrollador de Android y otras entradas de StackOverflow:
El problema aquí es el primer elemento, que se relaciona con el fondo de la barra de clasificación personalizada. Muchas entradas le dirán que configure la función layout_minHeight en un valor grande para evitar una desconexión espacial vertical entre el pulgar y su pista. Esta no fue la solución para mí: cuando se veía en una tableta, el fondo todavía se dibujaba en su tamaño más pequeño basado en el teléfono, por lo que la pista se posicionó consistentemente muy por encima del centro de la pista RatingBar. La solución es eliminar esta entrada en el dibujable RatingBar, por lo que ahora se ve así:
Luego, en la definición de estilo de la barra de clasificación personalizada, establezca layout_background en el dibujable de la pista. El mío se ve así:
Por lo tanto, para resumir, no establezca la función de fondo (pista) en su elemento de diseño de RatingBar personalizado, configúrelo en la función layout_background de su estilo de RatingBar personalizado. Esto asegura que la pista siempre esté centrada verticalmente en una barra de clasificación horizontal. (Recuerde, en esta barra de calificación personalizada, en lugar de usar estrellas u otras imágenes aisladas como calificación, estoy usando una línea de degradado que "crece" o "se encoge" horizontalmente para mostrar la calificación; esta línea de calificación usa un pulgar similar a SeekBar ejecutándose en una "pista" similar a SeekBar).
Puede utilizar la solución proporcionada por @erdomester para esto. Pero si tiene problemas con la altura de la barra de clasificación, puede usar la altura de los iconos de la barra de clasificación mediante programación.
En Kotlin,
val drawable = ContextCompat.getDrawable(context, R.drawable.rating_filled)
val drawableHeight = drawable.intrinsicHeight
rating_bar.layoutParams.height = drawableHeight
También enfrenté este problema, pero lo agregué android:minHeighten el diseño. Si cambia mediante programación, debe agregar rating_bar.requestLayout().
CoolMind
-11
Puede tener 5 visualizaciones de imágenes con una imagen defalut como una estrella que está vacía y llenar la barra de calificación con la mitad o la imagen completa en base a la calificación.
Respuestas:
Editar
Eche un vistazo a la calificación personalizada en motorola http://community.developer.motorola.com/t5/Android-App-Development-for/custom-rating-bar-style-using-android-s-ratingBar-small-style/ td-p / 10462
Actualizado
styles.xml
Esto debe estar ubicado en su carpeta de valores
<?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
Este archivo debe estar en la carpeta Drawable.
<?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
Este archivo debe estar dentro de la carpeta Drawable.
<?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_losed.xml
Este archivo debe estar ubicado en la carpeta Drawable.
<?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>
El archivo main.xml debería verse así:
<?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 debería verse así:
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(); } }); } }
He usado dos imágenes:
cookie.jpg
cookiee.jpg
Estas dos imágenes son del mismo tamaño, una se utiliza para identificar la barra de clasificación seleccionada y la otra para identificar la barra de clasificación no seleccionada
fuente
Necesito agregar mi solución, que es MUCHO más fácil que la anterior. Ni siquiera necesitamos usar estilos.
Crea un archivo selector en la carpeta dibujable:
custom_ratingbar_selector.xml
<?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_off" /> <item android:id="@android:id/secondaryProgress" android:drawable="@drawable/star_off" /> <item android:id="@android:id/progress" android:drawable="@drawable/star_on" /> </layer-list>
En el diseño, establezca el archivo selector como progressDrawable:
<RatingBar android:id="@+id/ratingBar2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:layout_marginTop="20dp" android:progressDrawable="@drawable/custom_ratingbar_selector" android:numStars="8" android:stepSize="0.2" android:rating="3.0" />
Y eso es todo lo que necesitamos.
fuente
primero agregue imágenes al dibujable:
la primera imagen "ratingbar_staroff.png" y la segunda "ratingbar_staron.png"
Después, crea "ratingbar.xml" en 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>
el siguiente xml lo mismo en 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_ llenado"
<?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>
lo siguiente que debe hacer es agregar estas líneas de código en res / valores / estilos
<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>
Ahora, ya se puede agregar estilo al recurso de la barra de calificación
<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"/>
finalmente en tu actividad solo se declara:
RatingBar ratingbar = (RatingBar) findViewById(R.id.ratingbar); ratingbar.setRating(3.67f);
fuente
res/values/styles
él está definiendoandroid:minHeight
yandroid:maxHeight
cambiando eso para hacer que el tamaño de la estrella sea grandeHacer la barra de clasificación personalizada con lista de capas y selectores es complejo, es mejor anular la clase RatingBar y crear una RatingBar personalizada. createBackgroundDrawableShape () es la función donde debe poner su estado vacío png y createProgressDrawableShape () es la función donde debe poner su estado relleno png.
Nota: este código no funcionará con svg por ahora.
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; } }
fuente
Para SVG
RatingBar
Solía encargo RatingBar vector dibujables superposición y la respuesta de erdomester aquí. Esta solución atraviesa todos los elementosSvgRatingBar
de diseño dentro de la vista de su diseño, porRecyclerView
lo que tiene una sobrecarga.SvgRatingBar.java:
import android.annotation.SuppressLint; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapShader; import android.graphics.Canvas; import android.graphics.Shader; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.ClipDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.LayerDrawable; import android.graphics.drawable.ShapeDrawable; import android.graphics.drawable.VectorDrawable; import android.graphics.drawable.shapes.RoundRectShape; import android.graphics.drawable.shapes.Shape; import android.os.Build; import android.util.AttributeSet; import android.view.Gravity; import androidx.appcompat.graphics.drawable.DrawableWrapper; import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat; import com.example.R; // Your R.java file for R.attr.ratingBarStyle. public class SvgRatingBar extends androidx.appcompat.widget.AppCompatRatingBar { private Bitmap sampleTile; public SvgRatingBar(Context context) { this(context, null); } public SvgRatingBar(Context context, AttributeSet attrs) { this(context, attrs, R.attr.ratingBarStyle); } public SvgRatingBar(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } private void init() { LayerDrawable drawable = (LayerDrawable) createTile(getProgressDrawable(), false); setProgressDrawable(drawable); } /** * Converts a drawable to a tiled version of itself. It will recursively * traverse layer and state list drawables. */ @SuppressLint("RestrictedApi") private Drawable createTile(Drawable drawable, boolean clip) { if (drawable instanceof DrawableWrapper) { Drawable inner = ((DrawableWrapper) drawable).getWrappedDrawable(); if (inner != null) { inner = createTile(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] = createTile(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 (sampleTile == null) { sampleTile = 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.START, ClipDrawable.HORIZONTAL) : shapeDrawable; } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && drawable instanceof VectorDrawable) { return createTile(getBitmapDrawableFromVectorDrawable(drawable), clip); } else if (drawable instanceof VectorDrawableCompat) { // API 19 support. return createTile(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 (sampleTile != null) { final int width = sampleTile.getWidth() * getNumStars(); setMeasuredDimension(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); } }
En tu diseño:
<com.example.common.control.SvgRatingBar android:id="@+id/rate" android:layout_width="wrap_content" android:layout_height="wrap_content" android:minHeight="13dp" android:numStars="5" android:progressDrawable="@drawable/rating_bar" android:rating="3.5" android:stepSize="0.01" />
También debe crear rating_bar.xml con dos elementos de diseño SVG:
<?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/ic_unfilled_star" /> <item android:id="@android:id/secondaryProgress" android:drawable="@drawable/ic_unfilled_star" /> <item android:id="@android:id/progress" android:drawable="@drawable/ic_filled_star" /> </layer-list>
Si ve en la vista Diseño / División solo una estrella, actualice el diseño:
En Kotlin.
import android.annotation.SuppressLint import android.content.Context import android.graphics.Bitmap import android.graphics.BitmapShader import android.graphics.Canvas import android.graphics.Shader import android.graphics.drawable.* import android.graphics.drawable.shapes.RoundRectShape import android.os.Build import android.util.AttributeSet import android.view.Gravity import androidx.appcompat.graphics.drawable.DrawableWrapper import androidx.appcompat.widget.AppCompatRatingBar import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat import com.example.R; // Your R.java file for R.attr.ratingBarStyle. class SvgRatingBar @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = R.attr.ratingBarStyle) : AppCompatRatingBar(context, attrs, defStyleAttr) { private var sampleTile: Bitmap? = null private val roundedCorners = floatArrayOf(5f, 5f, 5f, 5f, 5f, 5f, 5f, 5f) private val roundRectShape = RoundRectShape(roundedCorners, null, null) init { progressDrawable = createTile(progressDrawable, false) as LayerDrawable } /** * Converts a drawable to a tiled version of itself. It will recursively * traverse layer and state list drawables. */ private fun createTile(drawable: Drawable, clip: Boolean): Drawable = when { drawable is DrawableWrapper -> { @SuppressLint("RestrictedApi") var inner = drawable.wrappedDrawable if (inner != null) { inner = createTile(inner, clip) @SuppressLint("RestrictedApi") drawable.wrappedDrawable = inner } drawable } drawable is LayerDrawable -> { val n = drawable.numberOfLayers val outDrawables = arrayOfNulls<Drawable>(n) for (i in 0 until n) { val id = drawable.getId(i) outDrawables[i] = createTile(drawable.getDrawable(i), id == android.R.id.progress || id == android.R.id.secondaryProgress) } val newBg = LayerDrawable(outDrawables) for (i in 0 until n) { newBg.setId(i, drawable.getId(i)) } newBg } drawable is BitmapDrawable -> { val tileBitmap = drawable.bitmap if (sampleTile == null) { sampleTile = tileBitmap } val bitmapShader = BitmapShader(tileBitmap, Shader.TileMode.REPEAT, Shader.TileMode.CLAMP) val shapeDrawable = ShapeDrawable(roundRectShape).apply { paint.shader = bitmapShader paint.colorFilter = drawable.paint.colorFilter } if (clip) ClipDrawable(shapeDrawable, Gravity.START, ClipDrawable.HORIZONTAL) else shapeDrawable } Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && drawable is VectorDrawable -> { createTile(getBitmapDrawableFromVectorDrawable(drawable), clip) } drawable is VectorDrawableCompat -> { // Pre-Lollipop support. createTile(getBitmapDrawableFromVectorDrawable(drawable), clip) } else -> drawable } private fun getBitmapDrawableFromVectorDrawable(drawable: Drawable): BitmapDrawable { val bitmap = Bitmap.createBitmap(drawable.intrinsicWidth, drawable.intrinsicHeight, Bitmap.Config.ARGB_8888) val canvas = Canvas(bitmap) drawable.setBounds(0, 0, canvas.width, canvas.height) drawable.draw(canvas) return BitmapDrawable(resources, bitmap) } @Synchronized override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { super.onMeasure(widthMeasureSpec, heightMeasureSpec) if (sampleTile != null) { val width = sampleTile!!.width * numStars setMeasuredDimension(resolveSizeAndState(width, widthMeasureSpec, 0), measuredHeight) } } }
fuente
Puedes probar esta barra de clasificación con animaciones mucho mejores
SmileyEvaluación
fuente
Investigué la fuente original
y aquí está mi resultado.
styles.xml (res / valores)
<!-- 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 / dibujable)
<?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 / diseño)
<?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>
Este es el resultado.
layout_height
propiedad, porque si eswrap_content
así, dibujará líneas debajo de las estrellas. (en mi caso solo en una vista previa de Android Studio)fuente
Hice algo similar, una RatingBar con íconos de calificación individuales, estoy usando VectorDrawables para los íconos de calificación pero podrías usar cualquier tipo de elemento de diseño
https://github.com/manmountain/emoji-ratingbar
fuente
El siguiente código funciona:
@Override protected synchronized void onDraw(Canvas canvas) { int stars = getNumStars(); float rating = getRating(); try { bitmapWidth = getWidth() / stars; } catch (Exception e) { bitmapWidth = getWidth(); } float x = 0; for (int i = 0; i < stars; i++) { Bitmap bitmap; Resources res = getResources(); Paint paint = new Paint(); if ((int) rating > i) { bitmap = BitmapFactory.decodeResource(res, starColor); } else { bitmap = BitmapFactory.decodeResource(res, starDefault); } Bitmap scaled = Bitmap.createScaledBitmap(bitmap, getHeight(), getHeight(), true); canvas.drawBitmap(scaled, x, 0, paint); canvas.save(); x += bitmapWidth; } super.onDraw(canvas); }
fuente
Puede crear una barra de clasificación de material personalizada definiendo xml dibujable utilizando el icono de material de su elección y luego aplicando el elemento de diseño personalizado a la barra de clasificación utilizando el atributo progressDrawable.
Para obtener información sobre la personalización de la barra de clasificación, consulte http://www.zoftino.com/android-ratingbar-and-custom-ratingbar-example
A continuación, el XML dibujable usa el ícono de pulgar hacia arriba para la barra de calificación.
<?xml version="1.0" encoding="utf-8"?> <layer-list xmlns:android="http://schemas.android.com/apk/res/android"> <item android:id="@android:id/background"> <bitmap android:src="@drawable/thumb_up" android:tint="?attr/colorControlNormal" /> </item> <item android:id="@android:id/secondaryProgress"> <bitmap android:src="@drawable/thumb_up" android:tint="?attr/colorControlActivated" /> </item> <item android:id="@android:id/progress"> <bitmap android:src="@drawable/thumb_up" android:tint="?attr/colorControlActivated" /> </item> </layer-list>
fuente
Al crear una barra de clasificación personalizada que muestra una línea de degradado sólida que se ejecuta en una pista tipo SeekBar, en lugar de estrellas, también encontré un problema relacionado con el centrado vertical del fondo (pista dibujable). Este es el código de diseño defectuoso que usé originalmente (que generó el problema), como lo sugirió el desarrollador de Android y otras entradas de StackOverflow:
<?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>
El problema aquí es el primer elemento, que se relaciona con el fondo de la barra de clasificación personalizada. Muchas entradas le dirán que configure la función layout_minHeight en un valor grande para evitar una desconexión espacial vertical entre el pulgar y su pista. Esta no fue la solución para mí: cuando se veía en una tableta, el fondo todavía se dibujaba en su tamaño más pequeño basado en el teléfono, por lo que la pista se posicionó consistentemente muy por encima del centro de la pista RatingBar. La solución es eliminar esta entrada en el dibujable RatingBar, por lo que ahora se ve así:
<?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>
Luego, en la definición de estilo de la barra de clasificación personalizada, establezca layout_background en el dibujable de la pista. El mío se ve así:
<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>
(Anteriormente, la configuración de fondo aquí no estaba definida).
Esta es la entrada en mi diseño, que usa tanto el estilo como los elementos de diseño:
<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" />
Por lo tanto, para resumir, no establezca la función de fondo (pista) en su elemento de diseño de RatingBar personalizado, configúrelo en la función layout_background de su estilo de RatingBar personalizado. Esto asegura que la pista siempre esté centrada verticalmente en una barra de clasificación horizontal. (Recuerde, en esta barra de calificación personalizada, en lugar de usar estrellas u otras imágenes aisladas como calificación, estoy usando una línea de degradado que "crece" o "se encoge" horizontalmente para mostrar la calificación; esta línea de calificación usa un pulgar similar a SeekBar ejecutándose en una "pista" similar a SeekBar).
fuente
Puede utilizar la solución proporcionada por @erdomester para esto. Pero si tiene problemas con la altura de la barra de clasificación, puede usar la altura de los iconos de la barra de clasificación mediante programación.
En Kotlin,
val drawable = ContextCompat.getDrawable(context, R.drawable.rating_filled) val drawableHeight = drawable.intrinsicHeight rating_bar.layoutParams.height = drawableHeight
fuente
android:minHeight
en el diseño. Si cambia mediante programación, debe agregarrating_bar.requestLayout()
.Puede tener 5 visualizaciones de imágenes con una imagen defalut como una estrella que está vacía y llenar la barra de calificación con la mitad o la imagen completa en base a la calificación.
public View getView(int position, View convertView, ViewGroup parent) { LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); View grid=inflater.inflate(R.layout.griditem, parent, false); imageView=(ImageView)grid.findViewById(R.id.grid_prod); imageView.setImageResource(imgId[position]); imgoff =(ImageView)grid.findViewById(R.id.offer); tv=(TextView)grid.findViewById(R.id.grid_text); tv.setText(namesArr[position]); tv.setTextColor(Color.BLACK); tv.setPadding(0, 2, 0, 0); sta=(ImageView)grid.findViewById(R.id.imageView); sta1=(ImageView)grid.findViewById(R.id.imageView1); sta2=(ImageView)grid.findViewById(R.id.imageView2); sta3=(ImageView)grid.findViewById(R.id.imageView3); sta4=(ImageView)grid.findViewById(R.id.imageView4); Float rate=rateFArr[position]; if(rate==5 || rate==4.5) { sta.setImageResource(R.drawable.full__small); sta1.setImageResource(R.drawable.full__small); sta2.setImageResource(R.drawable.full__small); sta3.setImageResource(R.drawable.full__small); if(rate==4.5) { sta4.setImageResource(R.drawable.half_small); } else { sta4.setImageResource(R.drawable.full__small); } } if(rate==4 || rate==3.5) { sta.setImageResource(R.drawable.full__small); sta1.setImageResource(R.drawable.full__small); sta2.setImageResource(R.drawable.full__small); if(rate==3.5) { sta3.setImageResource(R.drawable.half_small); } else { sta3.setImageResource(R.drawable.full__small); } } if(rate==3 || rate==2.5) { sta.setImageResource(R.drawable.full__small); sta1.setImageResource(R.drawable.full__small); if(rate==2.5) { sta2.setImageResource(R.drawable.half_small); } else { sta2.setImageResource(R.drawable.full__small); } } if(rate==2 || rate==1.5) { sta.setImageResource(R.drawable.full__small); if(rate==1.5) { sta1.setImageResource(R.drawable.half_small); } else { sta1.setImageResource(R.drawable.full__small); } } if(rate==1 || rate==0.5) { if(rate==1) sta.setImageResource(R.drawable.full__small); else sta.setImageResource(R.drawable.half_small); } if(rate>5) { sta.setImageResource(R.drawable.full__small); sta1.setImageResource(R.drawable.full__small); sta2.setImageResource(R.drawable.full__small); sta3.setImageResource(R.drawable.full__small); sta4.setImageResource(R.drawable.full__small); } // rb=(RatingBar)findViewById(R.id.grid_rating); //rb.setRating(rateFArr[position]); return grid; }
fuente
RatingBar
widget. Además, esto no es lo que pide el OP.