Jak utworzyć pasek ocen niestandardowych w systemie Android
80
Witam, wszystko, czego potrzebuję, aby przeprowadzić oceny w mojej aplikacji ... Więc muszę utworzyć niestandardowy pasek ocen ... Czy ktoś może mi w tym pomóc?
Przydałyby się niektóre fragmenty kodu (np. Makiety celu), aby zobaczyć, gdzie jesteś do tej pory. Patrząc na Twój post, nie wydaje się, że jest to problem do rozwiązania, a raczej oferta pracy, opowiedz o tym.
<?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_filled.xml
Ten plik musi znajdować się w folderze do rysowania.
<?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>
To zadziałało dla mnie, po zmianie w pliku food_rating_bar_full.xml, identyfikator pozycji. Zastąpiłem <item android: id = "@ + android: id / background" na <item android: id = "@ android: id / background" (bez symbolu +).
to samo, co sugerował @erdomester, ale więcej kodu. UWAGA: Nie zapominaj, że nie działa z obrazami wektorowymi, tylko z .png!
Kirill Karmazin
1
Nie zadziałało, ale było dobrze, gdy zamieniłem: - android: id = "@ + id / background na @android: id / background - android: id =" @ + id / secondaryProgress "na android: id = "@ android: id / secondaryProgress" - android: id = "@ + id / progress" with android: id = "@ android: id / progress"
Haris Dautović
56
Muszę dodać moje rozwiązanie, które jest O WIELE łatwiejsze niż to powyżej. Nie musimy nawet używać stylów.
+1, ale przepraszam, to powinno działać, ale nie. Najpierw powiedziałeś, że dodajesz Selektor, ale w rzeczywistości dodajesz listę warstw. Po drugie, wypróbowałem dokładnie twój kod, ale wszystko, co otrzymałem, to pusty komponent (w ogóle brak obrazu). Może możesz odpowiedzieć na moje pytanie: stackoverflow.com/questions/14251092/... Dzięki: D
Blaze Tama
3
To proste podejście powoduje problemy z pozycjonowaniem paska oceny w układzie. Bardziej tradycyjne podejście do definiowania stylu jest lepsze.
javaxian
3
poniżej znajduje się linia ze wszystkimi gwiazdami na pasku oceny
Sunil
3
Działa świetnie, dzięki. UWAGA: Nie zapominaj, że nie działa z obrazami wektorowymi, tylko z .png!
Jest podobny do zaznaczonej odpowiedzi. Ale ma zdjęcia (pokazuje ułamkowe gwiazdki) i <! - pomija AndroidDomInspection -> na liście warstw. Zamiast tego możesz usunąć znak plusa „@ + android: id / background”, aby uniknąć błędów Androida. Należy również zmienić minHeight i maxHeight na 50dp.
CoolMind
Dzięki za rozwiązanie. czy mogę wiedzieć, jak dopasować rozmiar gwiazd? mój wyszedł bardzo mały. dzięki.
jay
@jay w the res/values/styleson definiuje android:minHeighti android:maxHeightzmienia to, aby zwiększyć rozmiar gwiazdy
Inzimam Tariq IT
7
Tworzenie niestandardowego paska oceny z listą warstw i selektorami jest skomplikowane, lepiej jest zastąpić klasę RatingBar i utworzyć niestandardowy RatingBar. createBackgroundDrawableShape () to funkcja, w której należy umieścić swój pusty stan png, a createProgressDrawableShape () to funkcja, w której należy umieścić wypełniony stan png.
Uwaga: ten kod na razie nie będzie działał z SVG.
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;
}
}
@AliRezaiyan: Ponieważ używamy naszych własnych rysunków, tj. Ic_star_full i ic_star_empty, możesz po prostu dodać puste miejsce (dopełnienie) po lewej i prawej stronie pliku PNG. Możesz użyć narzędzi takich jak GIMP lub Android Asset Studio, aby to osiągnąć, edytując plik PNG. Mam nadzieję że to pomoże.
Gaurav Saluja
Szukam sposobu programistycznego
Ali Rezaiyan
5
W przypadku SVGRatingBar użyłem nakładania niestandardowych wektorów RatingBar i odpowiedź erdomester tutaj. To rozwiązanie przechodzi przez wszystkie elementy rysunkowe wewnątrz SvgRatingBarwidoku układu, więc RecyclerViewma narzut.
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);
}
}
Zwróć uwagę, że dodałem rzeczywistą wysokość (13,4 dp) paska ratingowego we layout_heightwłaściwości, ponieważ jeśli tak, wrap_contentto narysuje linie poniżej gwiazdek. (w moim przypadku tylko w podglądzie Android Studio)
Zrobiłem coś podobnego, pasek RatingBar z indywidualnymi ikonami ocen. Używam VectorDrawables jako ikon ocen, ale możesz użyć dowolnego typu do rysowania
Możesz utworzyć niestandardowy pasek oceny materiału, definiując plik XML do rysowania za pomocą wybranej ikony materiału, a następnie stosując atrybut progressDrawable, stosując niestandardowy pasek oceny materiału.
Podczas tworzenia niestandardowego paska oceny, który wyświetla ciągłą linię gradientu biegnącą po torze podobnym do SeekBar, a nie gwiazd, napotkałem również problem związany z pionowym wyśrodkowaniem tła (ścieżka do rysowania). Oto wadliwy kod, którego użyłem pierwotnie (który spowodował problem), zgodnie z sugestią programisty Androida i innych wpisów StackOverflow:
Problem dotyczy pierwszego elementu, który dotyczy tła niestandardowego paska RatingBar. Wiele wpisów mówi o ustawieniu funkcji layout_minHeight na jakąś dużą wartość, aby uniknąć pionowego przestrzennego rozłączenia między kciukiem a jego ścieżką. To nie było dla mnie rozwiązanie - podczas oglądania na tablecie tło wciąż rysowało się do mniejszego rozmiaru w telefonie - więc ścieżka była konsekwentnie umieszczana znacznie powyżej środka toru RatingBar. Rozwiązaniem jest usunięcie tego wpisu z rysunkowego paska RatingBar, aby teraz wyglądał tak:
Podsumowując, nie ustawiaj funkcji tła (ścieżki) w niestandardowym drawable RatingBar, ale ustaw ją w funkcji layout_background własnego stylu RatingBar. Dzięki temu ścieżka jest zawsze wyśrodkowana w pionie na poziomym pasku RatingBar. (Pamiętaj, że w tym niestandardowym pasku RatingBar zamiast używać gwiazdek lub innych izolowanych obrazów jako oceny, używam linii gradientu, która „rośnie” lub „kurczy się” w poziomie, aby wyświetlić ocenę - ta linia oceny używa kciuka podobnego do paska SeekBar działa na „ścieżce” przypominającej SeekBar).
Możesz użyć do tego rozwiązania podanego przez @erdomester. Ale jeśli napotykasz problemy z wysokością paska oceny, możesz programowo użyć wysokości ikon na pasku oceny.
W Kotlinie
val drawable = ContextCompat.getDrawable(context, R.drawable.rating_filled)
val drawableHeight = drawable.intrinsicHeight
rating_bar.layoutParams.height = drawableHeight
Odpowiedzi:
Edytować
Spójrz na niestandardową ocenę w motorola http://community.developer.motorola.com/t5/Android-App-Development-for/custom-rating-bar-style-using-android-s-ratingBar-small-style/ td-p / 10462
Zaktualizowano
style.xml
Musi znajdować się w folderze wartości
<?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
Ten plik musi znajdować się w folderze do rysowania.
<?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
Ten plik musi znajdować się w folderze 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_filled.xml
Ten plik musi znajdować się w folderze do rysowania.
<?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>
Plik main.xml powinien wyglądać następująco:
<?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 powinien wyglądać następująco:
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(); } }); } }
Użyłem dwóch obrazów:
cookie.jpg
cookiee.jpg
Te dwa obrazy mają ten sam rozmiar, jeden służy do identyfikacji wybranego paska oceny, a drugi do identyfikowania niewybranego paska oceny
źródło
Muszę dodać moje rozwiązanie, które jest O WIELE łatwiejsze niż to powyżej. Nie musimy nawet używać stylów.
Utwórz plik selektora w folderze do rysowania:
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>
W układzie ustaw plik selektora jako 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" />
I to wszystko, czego potrzebujemy.
źródło
najpierw dodaj obrazy do rysowania:
pierwsze zdjęcie „ratingbar_staroff.png”, a drugie „ratingbar_staron.png”
Następnie utwórz plik „ratingbar.xml” na 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>
następny plik xml to samo na 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>
W następnej kolejności dodaj te wiersze kodu do 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>
Teraz możesz już dodać styl do zasobu ratingbar
<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"/>
na koniec tylko o swojej działalności zadeklaruj:
RatingBar ratingbar = (RatingBar) findViewById(R.id.ratingbar); ratingbar.setRating(3.67f);
źródło
res/values/styles
on definiujeandroid:minHeight
iandroid:maxHeight
zmienia to, aby zwiększyć rozmiar gwiazdyTworzenie niestandardowego paska oceny z listą warstw i selektorami jest skomplikowane, lepiej jest zastąpić klasę RatingBar i utworzyć niestandardowy RatingBar. createBackgroundDrawableShape () to funkcja, w której należy umieścić swój pusty stan png, a createProgressDrawableShape () to funkcja, w której należy umieścić wypełniony stan png.
Uwaga: ten kod na razie nie będzie działał z SVG.
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; } }
źródło
W przypadku SVG
RatingBar
użyłem nakładania niestandardowych wektorów RatingBar i odpowiedź erdomester tutaj. To rozwiązanie przechodzi przez wszystkie elementy rysunkowe wewnątrzSvgRatingBar
widoku układu, więcRecyclerView
ma narzut.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); } }
W swoim układzie:
<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" />
Musisz także utworzyć rating_bar.xml z dwoma rysunkami 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>
Jeśli widzisz w widoku Projekt / Podziel tylko jedną gwiazdkę, odśwież układ:
W Kotlinie.
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) } } }
źródło
Możesz wypróbować ten pasek oceny ze znacznie lepszymi animacjami
SmileyRating
źródło
Zbadałem oryginalne źródło
i oto mój wynik.
style.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>
To jest wynik.
layout_height
właściwości, ponieważ jeśli tak,wrap_content
to narysuje linie poniżej gwiazdek. (w moim przypadku tylko w podglądzie Android Studio)źródło
Zrobiłem coś podobnego, pasek RatingBar z indywidualnymi ikonami ocen. Używam VectorDrawables jako ikon ocen, ale możesz użyć dowolnego typu do rysowania
https://github.com/manmountain/emoji-ratingbar
źródło
Poniższy kod działa:
@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); }
źródło
Możesz utworzyć niestandardowy pasek oceny materiału, definiując plik XML do rysowania za pomocą wybranej ikony materiału, a następnie stosując atrybut progressDrawable, stosując niestandardowy pasek oceny materiału.
Aby uzyskać informacje na temat dostosowywania paska oceny, zobacz http://www.zoftino.com/android-ratingbar-and-custom-ratingbar-example
Poniższy plik XML z możliwością rysowania używa ikony kciuka w górę na pasku oceny.
<?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>
źródło
Podczas tworzenia niestandardowego paska oceny, który wyświetla ciągłą linię gradientu biegnącą po torze podobnym do SeekBar, a nie gwiazd, napotkałem również problem związany z pionowym wyśrodkowaniem tła (ścieżka do rysowania). Oto wadliwy kod, którego użyłem pierwotnie (który spowodował problem), zgodnie z sugestią programisty Androida i innych wpisów 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>
Problem dotyczy pierwszego elementu, który dotyczy tła niestandardowego paska RatingBar. Wiele wpisów mówi o ustawieniu funkcji layout_minHeight na jakąś dużą wartość, aby uniknąć pionowego przestrzennego rozłączenia między kciukiem a jego ścieżką. To nie było dla mnie rozwiązanie - podczas oglądania na tablecie tło wciąż rysowało się do mniejszego rozmiaru w telefonie - więc ścieżka była konsekwentnie umieszczana znacznie powyżej środka toru RatingBar. Rozwiązaniem jest usunięcie tego wpisu z rysunkowego paska RatingBar, aby teraz wyglądał tak:
<?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>
Następnie w definicji stylu niestandardowego RatingBar ustaw layout_background na ścieżkę do rysowania. Mój wygląda tak:
<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>
(Wcześniej ustawienie tła w tym miejscu było niezdefiniowane).
To jest wpis w moim układzie, który wykorzystuje zarówno styl, jak i elementy do rysowania:
<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" />
Podsumowując, nie ustawiaj funkcji tła (ścieżki) w niestandardowym drawable RatingBar, ale ustaw ją w funkcji layout_background własnego stylu RatingBar. Dzięki temu ścieżka jest zawsze wyśrodkowana w pionie na poziomym pasku RatingBar. (Pamiętaj, że w tym niestandardowym pasku RatingBar zamiast używać gwiazdek lub innych izolowanych obrazów jako oceny, używam linii gradientu, która „rośnie” lub „kurczy się” w poziomie, aby wyświetlić ocenę - ta linia oceny używa kciuka podobnego do paska SeekBar działa na „ścieżce” przypominającej SeekBar).
źródło
Możesz użyć do tego rozwiązania podanego przez @erdomester. Ale jeśli napotykasz problemy z wysokością paska oceny, możesz programowo użyć wysokości ikon na pasku oceny.
W Kotlinie
val drawable = ContextCompat.getDrawable(context, R.drawable.rating_filled) val drawableHeight = drawable.intrinsicHeight rating_bar.layoutParams.height = drawableHeight
źródło
android:minHeight
w układzie. Jeśli zmieniasz programowo, należy dodaćrating_bar.requestLayout()
.Możesz mieć 5 widoków obrazu z domyślnym obrazem jako gwiazdą, która jest pusta i wypełnij pasek oceny połową lub pełnym obrazem w oparciu o ocenę.
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; }
źródło
RatingBar
widget. Nie o to również prosi PO.