CheckBox i RadioButton
Jak budować formularze z polami wyboru: CheckBox pozwala zaznaczyć wiele opcji jednocześnie, RadioButton w grupie RadioGroup pozwala wybrać dokładnie jedną opcję.
Czym się różnią?
Oba to kontrolki wyboru – różni je jedna fundamentalna zasada: CheckBox działa niezależnie od innych pól, RadioButton w grupie RadioGroup wymusza wybór tylko jednej opcji.
CheckBox – wiele wyborów
Każde pole działa niezależnie. Użytkownik może zaznaczyć zero, jedno lub kilka pól jednocześnie. Typowe zastosowanie: lista zgód, lista dodatków do zamówienia, ustawienia.
RadioButton – jeden wybór
Przyciski muszą być w grupie RadioGroup. Zaznaczenie jednego automatycznie odznacza pozostałe. Typowe zastosowanie: wybór płci, wybór rozmiaru, wybór metody płatności.
| Cecha | CheckBox | RadioButton w RadioGroup |
|---|---|---|
| Ile opcji można wybrać? | Dowolna liczba (0 lub więcej) | Dokładnie jedna |
| Czy odznaczają się wzajemnie? | ❌ Nie | ✅ Tak – automatycznie |
| Wymaga grupy nadrzędnej? | ❌ Nie | ✅ Tak – RadioGroup |
| Jak sprawdzić stan? | isChecked() | isChecked() lub getCheckedRadioButtonId() |
Oba dziedziczą po klasie CompoundButton, która dziedziczy po Button, a ten po TextView. Dlatego wszystkie metody znane z TextView – setText(), setTextColor(), setVisibility() – działają też tutaj.
CheckBox – pola wyboru
Atrybuty XML
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:padding="24dp"> <CheckBox android:id="@+id/cbSer" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Ser" android:textSize="16sp" android:checked="false" <!-- domyślnie odznaczony --> android:layout_marginBottom="8dp" /> <CheckBox android:id="@+id/cbSalami" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Salami" android:textSize="16sp" android:checked="true" <!-- domyślnie zaznaczony --> android:layout_marginBottom="8dp" /> <CheckBox android:id="@+id/cbPieczarki" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Pieczarki" android:textSize="16sp" android:layout_marginBottom="24dp" /> <Button android:id="@+id/btnZamow" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Zamów" /> <TextView android:id="@+id/tvWynik" android:layout_width="match_parent" android:layout_height="wrap_content" android:textSize="16sp" android:padding="12dp" android:layout_marginTop="16dp" /> </LinearLayout>
Odczyt stanu w kodzie
Stan CheckBoxa sprawdzamy metodą isChecked() która zwraca true (zaznaczony) lub false (odznaczony). Wywołujemy ją najczęściej po kliknięciu przycisku.
// Sprawdź czy CheckBox jest zaznaczony if (cbSer.isChecked()) { // zaznaczony } // Typowy wzorzec – zbieramy zaznaczone opcje String dodatki = ""; if (cbSer.isChecked()) dodatki += "ser, "; if (cbSalami.isChecked()) dodatki += "salami, "; if (cbPieczarki.isChecked()) dodatki += "pieczarki, "; if (dodatki.isEmpty()) { tvWynik.setText("Brak dodatków"); } else { // Usuń ostatni przecinek i spację tvWynik.setText("Dodatki: " + dodatki.substring(0, dodatki.length() - 2)); }
// Kotlin: isChecked jako właściwość if (cbSer.isChecked) { /* zaznaczony */ } // Zbieramy zaznaczone opcje do listy val lista = mutableListOf<String>() if (cbSer.isChecked) lista.add("ser") if (cbSalami.isChecked) lista.add("salami") if (cbPieczarki.isChecked) lista.add("pieczarki") if (lista.isEmpty()) { tvWynik.text = "Brak dodatków" } else { // joinToString łączy elementy listy separatorem tvWynik.text = "Dodatki: ${lista.joinToString(", ")}" }
Nasłuch zmiany stanu – setOnCheckedChangeListener
Zamiast czekać na kliknięcie przycisku, możemy reagować na każdą zmianę stanu CheckBoxa w czasie rzeczywistym.
// isChecked – aktualny stan po zmianie (true = zaznaczony) cbSer.setOnCheckedChangeListener((buttonView, isChecked) -> { if (isChecked) { Toast.makeText(this, "Dodano ser", Toast.LENGTH_SHORT).show(); } else { Toast.makeText(this, "Usunięto ser", Toast.LENGTH_SHORT).show(); } });
cbSer.setOnCheckedChangeListener { buttonView, isChecked ->
if (isChecked) {
Toast.makeText(this, "Dodano ser", Toast.LENGTH_SHORT).show()
} else {
Toast.makeText(this, "Usunięto ser", Toast.LENGTH_SHORT).show()
}
}
buttonView to referencja do CheckBoxa który zmienił stan – możesz jej użyć tak samo jak View v w onClick, np. buttonView.getId() żeby sprawdzić który CheckBox się zmienił. isChecked to nowy stan – true jeśli właśnie zaznaczono, false jeśli odznaczono.
Operacje na CheckBox w kodzie
// Zaznacz / odznacz programistycznie cbSer.setChecked(true); cbSer.setChecked(false); // Przełącz stan (zaznaczony ↔ odznaczony) cbSer.toggle(); // Dezaktywuj pole (szare, nieaktywne) cbSer.setEnabled(false); // Zmień tekst obok checkboxa cbSer.setText("Ser (niedostępny)");
// Zaznacz / odznacz programistycznie cbSer.isChecked = true cbSer.isChecked = false // Przełącz stan cbSer.toggle() // Dezaktywuj pole cbSer.isEnabled = false // Zmień tekst cbSer.text = "Ser (niedostępny)"
Pełny przykład – zamówienie pizzy
public class MainActivity extends AppCompatActivity { CheckBox cbSer, cbSalami, cbPieczarki; Button btnZamow; TextView tvWynik; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initUI(); } public void initUI() { cbSer = findViewById(R.id.cbSer); cbSalami = findViewById(R.id.cbSalami); cbPieczarki = findViewById(R.id.cbPieczarki); btnZamow = findViewById(R.id.btnZamow); tvWynik = findViewById(R.id.tvWynik); btnZamow.setOnClickListener(v -> { String dodatki = ""; if (cbSer.isChecked()) dodatki += "ser, "; if (cbSalami.isChecked()) dodatki += "salami, "; if (cbPieczarki.isChecked()) dodatki += "pieczarki, "; if (dodatki.isEmpty()) { tvWynik.setText("Zamówiono: pizza bez dodatków"); } else { tvWynik.setText("Zamówiono: " + dodatki.substring(0, dodatki.length() - 2)); } }); } }
class MainActivity : AppCompatActivity() { lateinit var cbSer : CheckBox lateinit var cbSalami : CheckBox lateinit var cbPieczarki : CheckBox lateinit var btnZamow : Button lateinit var tvWynik : TextView override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) initUI() } fun initUI() { cbSer = findViewById(R.id.cbSer) cbSalami = findViewById(R.id.cbSalami) cbPieczarki = findViewById(R.id.cbPieczarki) btnZamow = findViewById(R.id.btnZamow) tvWynik = findViewById(R.id.tvWynik) btnZamow.setOnClickListener { val lista = mutableListOf<String>() if (cbSer.isChecked) lista.add("ser") if (cbSalami.isChecked) lista.add("salami") if (cbPieczarki.isChecked) lista.add("pieczarki") val tekst = if (lista.isEmpty()) "pizza bez dodatków" else lista.joinToString(", ") tvWynik.text = "Zamówiono: $tekst" } } }
RadioButton i RadioGroup
RadioButton sam w sobie zachowuje się podobnie do CheckBoxa – możesz zaznaczać i odznaczać niezależnie. Dopiero umieszczenie wielu RadioButtonów w RadioGroup sprawia że zaznaczenie jednego automatycznie odznacza pozostałe.
Jeśli umieścisz RadioButtony w zwykłym LinearLayout zamiast w RadioGroup, będą działać niezależnie jak CheckBoxy – można zaznaczyć kilka naraz. Zawsze umieszczaj RadioButtony w RadioGroup gdy chcesz wymusić wybór jednej opcji.
Atrybuty XML
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:padding="24dp"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Wybierz rozmiar:" android:textSize="16sp" android:layout_marginBottom="8dp" /> <!-- RadioGroup grupuje przyciski – zaznaczenie jednego odznacza resztę --> <RadioGroup android:id="@+id/rgRozmiar" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="vertical" <!-- vertical lub horizontal --> android:layout_marginBottom="24dp"> <RadioButton android:id="@+id/rbMaly" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Mały (25 cm)" android:textSize="16sp" /> <RadioButton android:id="@+id/rbSredni" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Średni (30 cm)" android:textSize="16sp" android:checked="true" /> <!-- domyślnie wybrany --> <RadioButton android:id="@+id/rbDuzy" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Duży (40 cm)" android:textSize="16sp" /> </RadioGroup> <Button android:id="@+id/btnZamow" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Zamów" /> <TextView android:id="@+id/tvWynik" android:layout_width="match_parent" android:layout_height="wrap_content" android:textSize="16sp" android:padding="12dp" android:layout_marginTop="16dp" /> </LinearLayout>
Odczyt zaznaczonej opcji
Mamy dwa sposoby sprawdzenia który RadioButton jest zaznaczony. Pierwszy – przez RadioGroup, drugi – bezpośrednio przez RadioButton.
// Sposób 1: przez RadioGroup – pobieramy id zaznaczonego przycisku int id = rgRozmiar.getCheckedRadioButtonId(); if (id == -1) { // Żaden przycisk nie jest zaznaczony Toast.makeText(this, "Wybierz rozmiar", Toast.LENGTH_SHORT).show(); return; } switch (id) { case R.id.rbMaly: tvWynik.setText("Wybrałeś: mały (25 cm)"); break; case R.id.rbSredni: tvWynik.setText("Wybrałeś: średni (30 cm)"); break; case R.id.rbDuzy: tvWynik.setText("Wybrałeś: duży (40 cm)"); break; } // Sposób 2: bezpośrednio przez RadioButton – isChecked() if (rbMaly.isChecked()) tvWynik.setText("Mały"); if (rbSredni.isChecked()) tvWynik.setText("Średni"); if (rbDuzy.isChecked()) tvWynik.setText("Duży");
// Sposób 1: przez RadioGroup val id = rgRozmiar.checkedRadioButtonId if (id == -1) { Toast.makeText(this, "Wybierz rozmiar", Toast.LENGTH_SHORT).show() return } when (id) { R.id.rbMaly -> tvWynik.text = "Wybrałeś: mały (25 cm)" R.id.rbSredni -> tvWynik.text = "Wybrałeś: średni (30 cm)" R.id.rbDuzy -> tvWynik.text = "Wybrałeś: duży (40 cm)" } // Sposób 2: bezpośrednio przez RadioButton if (rbMaly.isChecked) tvWynik.text = "Mały" if (rbSredni.isChecked) tvWynik.text = "Średni" if (rbDuzy.isChecked) tvWynik.text = "Duży"
Jeśli żaden RadioButton w grupie nie jest zaznaczony, getCheckedRadioButtonId() zwraca -1. Zawsze warto sprawdzić ten przypadek zanim przejdziesz do switch/when – chyba że w XML ustawiłeś jeden przycisk jako domyślnie zaznaczony przez android:checked="true".
Nasłuch zmiany wyboru
Listener rejestrujemy na RadioGroup, nie na poszczególnych RadioButtonach. Jeden listener obsługuje całą grupę.
rgRozmiar.setOnCheckedChangeListener((group, checkedId) -> {
// checkedId – id właśnie zaznaczonego RadioButton
switch (checkedId) {
case R.id.rbMaly:
Toast.makeText(this, "Mały – 25 cm", Toast.LENGTH_SHORT).show();
break;
case R.id.rbSredni:
Toast.makeText(this, "Średni – 30 cm", Toast.LENGTH_SHORT).show();
break;
case R.id.rbDuzy:
Toast.makeText(this, "Duży – 40 cm", Toast.LENGTH_SHORT).show();
break;
}
});
rgRozmiar.setOnCheckedChangeListener { group, checkedId ->
when (checkedId) {
R.id.rbMaly -> Toast.makeText(this, "Mały – 25 cm", Toast.LENGTH_SHORT).show()
R.id.rbSredni -> Toast.makeText(this, "Średni – 30 cm", Toast.LENGTH_SHORT).show()
R.id.rbDuzy -> Toast.makeText(this, "Duży – 40 cm", Toast.LENGTH_SHORT).show()
}
}
Operacje na RadioGroup i RadioButton w kodzie
// Zaznacz konkretny RadioButton przez id rgRozmiar.check(R.id.rbSredni); // Odznacz wszystkie (żaden nie zaznaczony) rgRozmiar.clearCheck(); // Pobierz id aktualnie zaznaczonego int zaznaczonyId = rgRozmiar.getCheckedRadioButtonId(); // Zmień orientację grupy na poziomą (można też w XML) rgRozmiar.setOrientation(LinearLayout.HORIZONTAL);
// Zaznacz konkretny RadioButton rgRozmiar.check(R.id.rbSredni) // Odznacz wszystkie rgRozmiar.clearCheck() // Pobierz id aktualnie zaznaczonego val zaznaczonyId = rgRozmiar.checkedRadioButtonId
Pełny przykład – zamówienie rozmiaru
public class MainActivity extends AppCompatActivity { RadioGroup rgRozmiar; RadioButton rbMaly, rbSredni, rbDuzy; Button btnZamow; TextView tvWynik; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initUI(); } public void initUI() { rgRozmiar = findViewById(R.id.rgRozmiar); rbMaly = findViewById(R.id.rbMaly); rbSredni = findViewById(R.id.rbSredni); rbDuzy = findViewById(R.id.rbDuzy); btnZamow = findViewById(R.id.btnZamow); tvWynik = findViewById(R.id.tvWynik); btnZamow.setOnClickListener(v -> { int id = rgRozmiar.getCheckedRadioButtonId(); if (id == -1) { Toast.makeText(this, "Wybierz rozmiar pizzy!", Toast.LENGTH_SHORT).show(); return; } String rozmiar = ""; switch (id) { case R.id.rbMaly: rozmiar = "Mały 25 cm"; break; case R.id.rbSredni: rozmiar = "Średni 30 cm"; break; case R.id.rbDuzy: rozmiar = "Duży 40 cm"; break; } tvWynik.setText("Zamówiono: pizza " + rozmiar); }); } }
class MainActivity : AppCompatActivity() { lateinit var rgRozmiar: RadioGroup lateinit var rbMaly : RadioButton lateinit var rbSredni : RadioButton lateinit var rbDuzy : RadioButton lateinit var btnZamow : Button lateinit var tvWynik : TextView override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) initUI() } fun initUI() { rgRozmiar = findViewById(R.id.rgRozmiar) rbMaly = findViewById(R.id.rbMaly) rbSredni = findViewById(R.id.rbSredni) rbDuzy = findViewById(R.id.rbDuzy) btnZamow = findViewById(R.id.btnZamow) tvWynik = findViewById(R.id.tvWynik) btnZamow.setOnClickListener { val id = rgRozmiar.checkedRadioButtonId if (id == -1) { Toast.makeText(this, "Wybierz rozmiar pizzy!", Toast.LENGTH_SHORT).show() return@setOnClickListener } val rozmiar = when (id) { R.id.rbMaly -> "Mały 25 cm" R.id.rbSredni -> "Średni 30 cm" R.id.rbDuzy -> "Duży 40 cm" else -> "" } tvWynik.text = "Zamówiono: pizza $rozmiar" } } }
CheckBox vs RadioButton – jak wybrać?
Zasada jest prosta: zadaj sobie pytanie „Ile opcji może wybrać użytkownik?”
| Pytanie | Użyj | Przykład |
|---|---|---|
| Może zaznaczyć zero lub więcej opcji? | CheckBox | Dodatki do pizzy, lista zgód, ustawienia powiadomień |
| Musi wybrać dokładnie jedną opcję? | RadioButton + RadioGroup | Rozmiar, płeć, metoda dostawy, kategoria |
W zadaniach egzaminacyjnych RadioButtony zawsze muszą być w RadioGroup – bez niej można zaznaczyć kilka naraz i to jest błąd. Pamiętaj też o sprawdzeniu getCheckedRadioButtonId() == -1 gdy żaden nie jest domyślnie zaznaczony. Dla CheckBoxów standardowy wzorzec to sprawdzenie isChecked() każdego pola osobno po kliknięciu przycisku.
Podsumowanie
| Operacja | Java | Kotlin |
|---|---|---|
| Znajdź CheckBox | cb = findViewById(R.id.cb); |
cb = findViewById(R.id.cb) |
| Sprawdź stan | cb.isChecked() |
cb.isChecked |
| Zaznacz / odznacz | cb.setChecked(true/false) |
cb.isChecked = true/false |
| Przełącz stan | cb.toggle() |
cb.toggle() |
| Listener zmiany stanu | cb.setOnCheckedChangeListener((btn, isChecked) -> { }) |
cb.setOnCheckedChangeListener { btn, isChecked -> } |
| Który RadioButton zaznaczony? | rg.getCheckedRadioButtonId() |
rg.checkedRadioButtonId |
| Zaznacz RadioButton przez id | rg.check(R.id.rbNazwa) |
rg.check(R.id.rbNazwa) |
| Odznacz wszystkie w grupie | rg.clearCheck() |
rg.clearCheck() |
| Listener zmiany w RadioGroup | rg.setOnCheckedChangeListener((group, checkedId) -> { }) |
rg.setOnCheckedChangeListener { group, checkedId -> } |
Zadanie dla uczniów
Formularz zamówienia pizzy
- Stwórz formularz z
RadioGroupdo wyboru rozmiaru: Mała (20 zł), Średnia (30 zł), Duża (40 zł) - Dodaj trzy
CheckBoxy z dodatkami: Ser (+3 zł), Salami (+5 zł), Pieczarki (+4 zł) - Dodaj przycisk „Oblicz cenę” i
TextViewna wynik - Po kliknięciu przycisku: sprawdź czy rozmiar jest wybrany (jeśli nie – Toast), zsumuj cenę bazową i zaznaczone dodatki, wyświetl w TextView: „Cena: X zł”
- Zadeklaruj wszystkie widoki jako pola klasy, zainicjalizuj w
initUI()
⭐ Bonus 1: dodaj setOnCheckedChangeListener na RadioGroup który na bieżąco aktualizuje TextView z ceną bazową
⭐⭐ Bonus 2: dodaj przycisk „Resetuj” który odznacza wszystkie CheckBoxy i czyści wybór w RadioGroup (clearCheck())
⭐⭐⭐ Bonus: połącz z poprzednim materiałem – po kliknięciu „Zamów” pokaż AlertDialog z potwierdzeniem zamówienia i ceną