school-it.pl // android + kotlin // rozdział 4 // lekcja 16
RatingBar — ocena w gwiazdkach
RatingBar to rząd gwiazdek, którymi użytkownik wystawia ocenę — jak w sklepie z aplikacjami czy przy recenzji filmu. Ta sama kontrolka może też tylko pokazywać ocenę, na przykład średnią z wielu głosów. W tej lekcji zbudujesz ankietę oceny szkolnej stołówki: trzy kategorie oceniane gwiazdkami i średnia pokazana w małych gwiazdkach tylko do odczytu.
Czego się dziś nauczysz
- Ustawisz liczbę gwiazdek i krok oceny (całe lub połówki).
- Odczytasz ocenę jako liczbę
Float. - Zareagujesz na zmianę oceny.
- Pokażesz ocenę tylko do odczytu w małych gwiazdkach.
- Policzysz średnią z kilku ocen i zamienisz ją na opis słowny.
Gwiazdki w XML
<RatingBar
android:id="@+id/rbFilm"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:numStars="5"
android:stepSize="0.5"
android:rating="3.5" />
| Atrybut | Znaczenie |
|---|---|
android:numStars | Liczba gwiazdek. |
android:stepSize | Krok oceny: 1 — całe gwiazdki, 0.5 — połówki. |
android:rating | Ocena na starcie (0 = brak oceny). |
android:isIndicator="true" | Tylko do odczytu — palcem nie da się zmienić. |
android:progressTint | Kolor wypełnionych gwiazdek. |
Szerokość tylko wrap_content
Przy layout_width="match_parent" RatingBar dorysuje tyle gwiazdek, ile zmieści się na szerokości ekranu — numStars zostanie zignorowane. Szerokość ma być zawsze wrap_content.
Tylko do odczytu
Średnią ocen, ocenę z bazy czy ocenę innego użytkownika pokazujesz w RatingBar z isIndicator="true". Zwykle jest wtedy mniejszy — służy do tego gotowy styl:
<RatingBar
android:id="@+id/rbSrednia"
style="?android:attr/ratingBarStyleSmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:isIndicator="true"
android:numStars="5"
android:stepSize="0.1" />
krok 0.1 pozwala pokazać średnią dokładnie — gwiazdka wypełnia się częściowo
Ocena w kodzie
val ocena = rbFilm.rating // Float, np. 3.5
rbFilm.rating = 0f // wyczyść ocenę
rbFilm.setOnRatingBarChangeListener { _, nowaOcena, odUzytkownika ->
tvInfo.text = "Twoja ocena: $nowaOcena"
}
Ocena jest typu Float (liczba z częścią ułamkową) — nawet przy całych gwiazdkach to 4.0, a nie 4. Listener dostaje trzy wartości: kontrolkę, nową ocenę i informację, czy zmienił ją użytkownik (true), czy kod (false).
Ocena wyświetlona wprost wygląda jak „4.0”. Do napisu lepiej użyć String.format("%.1f", ocena) — da „4,0” z polskim przecinkiem — albo zamienić całe gwiazdki na liczbę całkowitą: ocena.toInt().
Ocena stołówki
Uczeń ocenia dzisiejszy obiad w trzech kategoriach: smak, wielkość porcji i obsługa (całe gwiazdki). Pod każdą kategorią pojawia się opis słowny. Na dole średnia w małych gwiazdkach tylko do odczytu i liczbowo. Wyślij jest aktywny dopiero po ocenieniu wszystkich trzech kategorii. Przycisk Wyczyść zeruje oceny.
Przygotowanie projektu
- Nowy projekt Empty Views Activity, nazwa
OcenaStolowki, Kotlin, API 24. - Podmień
activity_main.xmliMainActivity.kt.
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Oceń dzisiejszy obiad"
android:textSize="24sp"
android:textStyle="bold" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="Smak"
android:textStyle="bold" />
<RatingBar
android:id="@+id/rbSmak"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:numStars="5"
android:stepSize="1" />
<TextView
android:id="@+id/tvSmak"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:text="Wielkość porcji"
android:textStyle="bold" />
<RatingBar
android:id="@+id/rbPorcja"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:numStars="5"
android:stepSize="1" />
<TextView
android:id="@+id/tvPorcja"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:text="Obsługa"
android:textStyle="bold" />
<RatingBar
android:id="@+id/rbObsluga"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:numStars="5"
android:stepSize="1" />
<TextView
android:id="@+id/tvObsluga"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<!-- średnia: małe gwiazdki tylko do odczytu + liczba -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Średnia: "
android:textSize="18sp" />
<RatingBar
android:id="@+id/rbSrednia"
style="?android:attr/ratingBarStyleSmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:isIndicator="true"
android:numStars="5"
android:stepSize="0.1" />
<TextView
android:id="@+id/tvSrednia"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:textSize="18sp"
android:textStyle="bold" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:orientation="horizontal">
<Button
android:id="@+id/btnWyczysc"
style="@style/Widget.Material3.Button.OutlinedButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginEnd="8dp"
android:text="Wyczyść" />
<Button
android:id="@+id/btnWyslij"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:enabled="false"
android:text="Wyślij" />
</LinearLayout>
<TextView
android:id="@+id/tvPotwierdzenie"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:textColor="#2E7D32" />
</LinearLayout>
</ScrollView>
package pl.schoolit.ocenastolowki
import android.os.Bundle
import android.widget.Button
import android.widget.RatingBar
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val rbSmak = findViewById<RatingBar>(R.id.rbSmak)
val rbPorcja = findViewById<RatingBar>(R.id.rbPorcja)
val rbObsluga = findViewById<RatingBar>(R.id.rbObsluga)
val rbSrednia = findViewById<RatingBar>(R.id.rbSrednia)
val tvSmak = findViewById<TextView>(R.id.tvSmak)
val tvPorcja = findViewById<TextView>(R.id.tvPorcja)
val tvObsluga = findViewById<TextView>(R.id.tvObsluga)
val tvSrednia = findViewById<TextView>(R.id.tvSrednia)
val tvPotwierdzenie = findViewById<TextView>(R.id.tvPotwierdzenie)
val btnWyczysc = findViewById<Button>(R.id.btnWyczysc)
val btnWyslij = findViewById<Button>(R.id.btnWyslij)
// ocena 0–5 → opis; indeks tablicy = liczba gwiazdek
val opisy = arrayOf("nie oceniono", "bardzo słabo", "słabo", "przeciętnie", "bardzo dobrze", "rewelacja")
fun przelicz() {
tvPotwierdzenie.text = ""
tvSmak.text = opisy[rbSmak.rating.toInt()]
tvPorcja.text = opisy[rbPorcja.rating.toInt()]
tvObsluga.text = opisy[rbObsluga.rating.toInt()]
val wszystkieOcenione = rbSmak.rating > 0 && rbPorcja.rating > 0 && rbObsluga.rating > 0
btnWyslij.isEnabled = wszystkieOcenione
if (wszystkieOcenione) {
val srednia = (rbSmak.rating + rbPorcja.rating + rbObsluga.rating) / 3
rbSrednia.rating = srednia
tvSrednia.text = String.format("%.1f", srednia)
} else {
rbSrednia.rating = 0f
tvSrednia.text = "oceń wszystko"
}
}
przelicz()
rbSmak.setOnRatingBarChangeListener { _, _, _ -> przelicz() }
rbPorcja.setOnRatingBarChangeListener { _, _, _ -> przelicz() }
rbObsluga.setOnRatingBarChangeListener { _, _, _ -> przelicz() }
btnWyczysc.setOnClickListener {
rbSmak.rating = 0f
rbPorcja.rating = 0f
rbObsluga.rating = 0f
}
btnWyslij.setOnClickListener {
tvPotwierdzenie.text = "Dziękujemy! Twoja średnia: ${tvSrednia.text}"
}
}
}
Co dzieje się po kolei
- Trzy oceniające
RatingBarmają krok 1 — tylko całe gwiazdki. Na starcieratingwynosi 0, czyli „nie oceniono”. rating.toInt()zamienia ocenę 4.0 na liczbę 4 — indeks w tablicyopisy. Pozycja 0 to „nie oceniono”, dlatego tablica ma 6 elementów.przelicz()sprawdza, czy wszystkie oceny są większe od 0. Tylko wtedy liczy średnią i włącza Wyślij.- Średnia (np. 3,67) trafia do małego
RatingBarz krokiem 0.1 — gwiazdka wypełnia się częściowo. - Wyczyść ustawia oceny na
0f. Listener reaguje także na zmiany z kodu, więc opisy i średnia odświeżają się same.
Sprawdź trzy rzeczy
- Przesuń palcem po gwiazdkach zamiast w nie stukać. Ocena zmienia się w trakcie przesuwania.
- Dotknij małych gwiazdek średniej — nic się nie dzieje, bo mają
isIndicator="true". - Zmień w
rbSmakszerokość namatch_parent. Gwiazdek będzie więcej niż pięć.
Zestawienie elementów
| Element | Typ | Co robi |
|---|---|---|
<RatingBar> | kontrolka | Ocena w gwiazdkach. |
android:numStars, stepSize, rating | atrybuty | Liczba gwiazdek, krok, ocena startowa. |
android:isIndicator="true" | atrybut | Tylko do odczytu. |
style="?android:attr/ratingBarStyleSmall" | styl | Małe gwiazdki. |
rb.rating | Float | Odczyt i ustawienie oceny. |
rb.setOnRatingBarChangeListener { _, ocena, odUzytkownika -> } | metoda | Kod po zmianie oceny. |
liczba.toInt() | Int | Liczba całkowita (obcina część ułamkową). |
0f | Float | Zero typu Float. |
Zanim utkniesz
| Zapis / objaw | Problem |
|---|---|
więcej gwiazdek niż numStars | Szerokość match_parent lub waga. Ustaw wrap_content. |
rb.rating = 3 | Błąd kompilacji: oczekiwany Float. Pisz 3f. |
| „Twoja ocena: 4.0” zamiast „4” | Ocena to Float. Użyj toInt() albo String.format. |
| średnia pokazuje się w całych gwiazdkach | Wskaźnik ma domyślny krok. Ustaw stepSize="0.1". |
| użytkownik może zmienić średnią | Brak isIndicator="true". |
opisy[rb.rating.toInt()] z tablicą 5-elementową | Przy 5 gwiazdkach awaria ArrayIndexOutOfBoundsException — potrzebne miejsce także na 0. |
Zadania
Każde zadanie wykonaj w nowym projekcie Empty Views Activity. Cały kod pisz w onCreate. Wskazówkę otwórz dopiero wtedy, gdy utkniesz.
Tytuł filmu, RatingBar z połówkami gwiazdek i napis „Twoja ocena: 3,5 / 5”, zmieniający się przy każdej zmianie. Ocena 5 dodatkowo pokazuje „Arcydzieło!”.
pokaż wskazówkę
stepSize="0.5", napis:String.format("Twoja ocena: %.1f / 5", ocena).
RatingBar z sześcioma gwiazdkami jako ocena szkolna 1–6. Pod spodem słownie („dopuszczający”, „dostateczny”…) i kolor napisu zależny od oceny. Ocena 0 jest niedozwolona — po wyczyszczeniu gwiazdek ustaw 1.
pokaż wskazówkę
- W listenerze:
if (ocena < 1f) rb.rating = 1f. Ta zmiana wywoła listener jeszcze raz — tym razem z oceną 1.
Ocena aplikacji: duże gwiazdki do głosowania i przycisk Oddaj głos. Każdy głos dolicza się do średniej pokazanej w małych gwiazdkach i liczbowo („4,3 · 12 głosów”). Po oddaniu głosu duże gwiazdki się zerują.
pokaż wskazówkę
- Zapamiętaj sumę ocen (
var suma = 0f) i liczbę głosów. Średnia:suma / glosy.
Ekran ustawień gry: RatingBar z trzema gwiazdkami (całe) jako poziom trudności. Zależnie od poziomu zmienia się opis („łatwy: 5 żyć, wolni przeciwnicy”…) i kolor gwiazdek (zielony, pomarańczowy, czerwony).
pokaż wskazówkę
- Kolor z kodu:
rb.progressTintList = ColorStateList.valueOf(Color.parseColor("#…"))(importandroid.content.res.ColorStateList).
Trzy słuchawki z nazwą i ceną, każde z własnymi gwiazdkami. Pod spodem automatyczny ranking: „1. Model B (4,5) 2. Model A (3,0) 3. Model C (2,5)”, aktualizowany po każdej zmianie oceny. Produkty bez oceny nie biorą udziału w rankingu.
pokaż wskazówkę
- Zbuduj listę par
mutableListOf(Pair("Model A", rbA.rating), …), odfiltruj zera i posortuj:sortedByDescending { it.second }.
Formularz recenzji: gwiazdki (połówki), pole „Tytuł” i pole „Treść” (wielowierszowe). Reguły: ocena co najmniej 0,5; przy ocenie ≤ 2 treść musi mieć co najmniej 30 znaków (pod polem licznik „12 / 30”); tytuł 3–40 znaków. Opublikuj jest aktywny tylko przy spełnionych regułach, a pod formularzem pojawia się podgląd recenzji z gwiazdkami tylko do odczytu.
pokaż wskazówkę
- Na zmianę tekstu w polu reaguje
et.addTextChangedListener { … }(importandroidx.core.widget.addTextChangedListener). Wywołuj z niego tę samą funkcję sprawdzającą co z gwiazdek.
Co trzeba zapamiętać
RatingBar:numStars— ile gwiazdek,stepSize— krok, szerokość zawszewrap_content.- Ocena to
Float: odczytrb.rating, zapisrb.rating = 3f. - Zmianę obsługuje
setOnRatingBarChangeListener— także zmianę z kodu. - Ocenę tylko do pokazania wyświetlaj z
isIndicator="true"i stylemratingBarStyleSmall. - Średnią pokazuj z krokiem 0.1, żeby gwiazdka wypełniała się częściowo.
Dokumentacja: RatingBar.