Przejdź do treści
School IT / Przestrzeń do nauki

school-it.pl // android + kotlin // rozdział 3 // lekcja 11

Switch — włącz / wyłącz

Switch (przełącznik) wygląda jak suwak włącznika światła. Logicznie działa tak samo jak CheckBox — ma dwa stany — ale mówi użytkownikowi coś innego: zmiana działa od razu, bez przycisku „Zapisz”. Dlatego przełączniki spotkasz głównie w ustawieniach. W tej lekcji zbudujesz ekran ustawień z podglądem, który zmienia się w chwili przesunięcia przełącznika.

Kotlin XML MaterialSwitch isChecked setOnCheckedChangeListener 1 × 45 min
CEL LEKCJI

Czego się dziś nauczysz

  • Wybierzesz właściwą klasę przełącznika dla motywu Material 3.
  • Ustawisz stan początkowy i podpis przełącznika.
  • Zareagujesz na przełączenie od razu zmieniając wygląd ekranu.
  • Uzależnisz jeden przełącznik od drugiego.
  • Odróżnisz sytuacje dla Switch i dla CheckBox.
TEORIA

Trzy przełączniki Androida

Android przez lata dorobił się trzech wersji przełącznika. Wszystkie działają tak samo w kodzie, różnią się wyglądem:

Znacznik XMLKiedy
<Switch>Najstarszy, z samego Androida. Wygląda inaczej niż reszta aplikacji Material 3. Android Studio podkreśla go ostrzeżeniem.
<androidx.appcompat.widget.SwitchCompat>Wersja z biblioteki AppCompat — w starszych projektach.
<com.google.android.material.materialswitch.MaterialSwitch>Ten używamy. Przełącznik Material 3 pasujący do motywu nowych projektów.

Długa nazwa to pełna ścieżka do klasy z biblioteki Material, którą szablon projektu ma już dołączoną. Android Studio podpowie ją po wpisaniu <MaterialSw.

TEORIA

MaterialSwitch w XML

przełącznik na całą szerokość, włączony
<com.google.android.material.materialswitch.MaterialSwitch
    android:id="@+id/swPowiadomienia"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:checked="true"
    android:text="Powiadomienia" />

Przy szerokości match_parent podpis jest po lewej, a suwak przy prawej krawędzi — tak wyglądają ustawienia w telefonie. Cały wiersz reaguje na dotyk.

12:30
Powiadomienia
Tryb samolotowy
Wyłączony (enabled=”false”)
MaterialSwitch: włączony, wyłączony, nieaktywny
TEORIA

Przełącznik w kodzie

W kodzie przełącznik zachowuje się dokładnie jak CheckBox z lekcji 09 — oba dziedziczą po tej samej klasie CompoundButton („przycisk dwustanowy”):

MainActivity.kt — fragment onCreate
val swCiemny = findViewById<MaterialSwitch>(R.id.swCiemny)

swCiemny.setOnCheckedChangeListener { _, wlaczony ->
    if (wlaczony) {
        karta.setBackgroundColor(Color.parseColor("#1D1B20"))
    } else {
        karta.setBackgroundColor(Color.WHITE)
    }
}

swCiemny.isChecked = true          // zmiana z kodu — listener też się wykona

Klasa MaterialSwitch wymaga importu com.google.android.material.materialswitch.MaterialSwitch — Alt+Enter doda go sam.

TEORIA

Switch czy CheckBox?

SwitchCheckBox
skutekdziała natychmiastzatwierdzany przyciskiem
znaczeniewłącz / wyłącz funkcjęwybierz z listy / zgódź się
przykładWi-Fi, tryb ciemny, dźwiękidodatki do pizzy, zgoda na regulamin

Jeśli po przesunięciu przełącznika użytkownik musi jeszcze kliknąć „Zapisz”, to znak, że powinien to być CheckBox.

PRZYKŁAD

Ustawienia z podglądem

Na górze ekranu jest podgląd — karta z przykładową wiadomością. Pod nią cztery przełączniki: tryb ciemny zmienia kolory karty, duża czcionka zmienia rozmiar tekstu, „Pokaż godzinę” chowa i pokazuje godzinę wiadomości, a „Powiadomienia” włącza drugi, zależny przełącznik „Dźwięk powiadomień”. Każda zmiana działa od razu.

21:00
Ustawienia
Anna
Jutro sprawdzian z Kotlina — powtarzamy razem?
20:58
Tryb ciemny
Duża czcionka
Pokaż godzinę
Powiadomienia
Dźwięk powiadomień
Powiadomienia wyłączone
start: jasny podgląd, dźwięk nieaktywny
→włączenie
trzech
przełączników
21:01
Ustawienia
Anna
Jutro sprawdzian z Kotlina — powtarzamy razem?
20:58
Tryb ciemny
Duża czcionka
Pokaż godzinę
Powiadomienia
Dźwięk powiadomień
Powiadomienia: bez dźwięku
ciemna karta, większy tekst, dźwięk aktywny

przełącznik „Dźwięk” działa tylko przy włączonych powiadomieniach

Przygotowanie projektu

  1. Nowy projekt Empty Views Activity, nazwa Ustawienia, Kotlin, API 24.
  2. Podmień activity_main.xml i MainActivity.kt.
res/layout/activity_main.xml — pełny plik
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:layout_margin="16dp"
    android:fitsSystemWindows="true">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Ustawienia"
        android:textSize="24sp"
        android:textStyle="bold" />

    <!-- podgląd wiadomości -->
    <LinearLayout
        android:id="@+id/karta"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="12dp"
        android:background="#FFFFFF"
        android:orientation="vertical"
        android:padding="16dp">

        <TextView
            android:id="@+id/tvNadawca"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="Anna"
            android:textStyle="bold" />

        <TextView
            android:id="@+id/tvTresc"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="Jutro sprawdzian z Kotlina — powtarzamy razem?" />

        <TextView
            android:id="@+id/tvGodzina"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:gravity="end"
            android:text="20:58"
            android:textSize="12sp" />
    </LinearLayout>

    <com.google.android.material.materialswitch.MaterialSwitch
        android:id="@+id/swCiemny"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="16dp"
        android:text="Tryb ciemny" />

    <com.google.android.material.materialswitch.MaterialSwitch
        android:id="@+id/swDuza"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Duża czcionka" />

    <com.google.android.material.materialswitch.MaterialSwitch
        android:id="@+id/swGodzina"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:checked="true"
        android:text="Pokaż godzinę" />

    <com.google.android.material.materialswitch.MaterialSwitch
        android:id="@+id/swPowiadomienia"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Powiadomienia" />

    <!-- zależny: wcięty i nieaktywny, dopóki powiadomienia są wyłączone -->
    <com.google.android.material.materialswitch.MaterialSwitch
        android:id="@+id/swDzwiek"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginStart="24dp"
        android:enabled="false"
        android:text="Dźwięk powiadomień" />

    <TextView
        android:id="@+id/tvStan"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="8dp"
        android:textSize="14sp" />

</LinearLayout>
MainActivity.kt — pełny plik
package pl.schoolit.ustawienia

import android.graphics.Color
import android.os.Bundle
import android.widget.LinearLayout
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.isVisible
import com.google.android.material.materialswitch.MaterialSwitch

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val karta = findViewById<LinearLayout>(R.id.karta)
        val tvNadawca = findViewById<TextView>(R.id.tvNadawca)
        val tvTresc = findViewById<TextView>(R.id.tvTresc)
        val tvGodzina = findViewById<TextView>(R.id.tvGodzina)
        val tvStan = findViewById<TextView>(R.id.tvStan)
        val swCiemny = findViewById<MaterialSwitch>(R.id.swCiemny)
        val swDuza = findViewById<MaterialSwitch>(R.id.swDuza)
        val swGodzina = findViewById<MaterialSwitch>(R.id.swGodzina)
        val swPowiadomienia = findViewById<MaterialSwitch>(R.id.swPowiadomienia)
        val swDzwiek = findViewById<MaterialSwitch>(R.id.swDzwiek)

        // opis stanu powiadomień pod przełącznikami
        fun pokazStanPowiadomien() {
            if (!swPowiadomienia.isChecked) {
                tvStan.text = "Powiadomienia wyłączone"
            } else if (swDzwiek.isChecked) {
                tvStan.text = "Powiadomienia: z dźwiękiem"
            } else {
                tvStan.text = "Powiadomienia: bez dźwięku"
            }
        }

        pokazStanPowiadomien()

        swCiemny.setOnCheckedChangeListener { _, wlaczony ->
            if (wlaczony) {
                karta.setBackgroundColor(Color.parseColor("#1D1B20"))
                tvNadawca.setTextColor(Color.WHITE)
                tvTresc.setTextColor(Color.parseColor("#E6E0E9"))
                tvGodzina.setTextColor(Color.parseColor("#CAC4D0"))
            } else {
                karta.setBackgroundColor(Color.WHITE)
                tvNadawca.setTextColor(Color.BLACK)
                tvTresc.setTextColor(Color.BLACK)
                tvGodzina.setTextColor(Color.GRAY)
            }
        }

        swDuza.setOnCheckedChangeListener { _, wlaczony ->
            val rozmiar = if (wlaczony) 20f else 14f
            tvNadawca.textSize = rozmiar
            tvTresc.textSize = rozmiar
            tvGodzina.textSize = rozmiar - 2
        }

        swGodzina.setOnCheckedChangeListener { _, wlaczony ->
            tvGodzina.isVisible = wlaczony           // false: napis znika
        }

        swPowiadomienia.setOnCheckedChangeListener { _, wlaczony ->
            swDzwiek.isEnabled = wlaczony
            if (!wlaczony) {
                swDzwiek.isChecked = false           // bez powiadomień nie ma dźwięku
            }
            pokazStanPowiadomien()
        }

        swDzwiek.setOnCheckedChangeListener { _, _ ->
            pokazStanPowiadomien()
        }
    }
}

Co dzieje się po kolei

  • Każdy przełącznik ma własny listener i zmienia tylko „swoją” część podglądu. Nie ma przycisku „Zapisz” — tak działają ustawienia.
  • Tryb ciemny zmienia tło karty i kolor trzech napisów. Oba stany muszą być obsłużone — wyłączenie przełącznika przywraca jasne kolory.
  • if (wlaczony) 20f else 14f to if zwracający wartość — wybiera rozmiar w jednej linii.
  • tvGodzina.isVisible = wlaczony chowa napis razem z miejscem, które zajmował (karta robi się niższa).
  • swDzwiek ma w XML enabled="false". Włączają go dopiero powiadomienia, a ich wyłączenie dodatkowo wyłącza dźwięk.
  • pokazStanPowiadomien() opisuje kombinację dwóch przełączników — dlatego wywołują ją oba listenery.

Sprawdź trzy rzeczy

  1. Włącz powiadomienia i dźwięk, potem wyłącz powiadomienia. Dźwięk sam się wyłączy i zrobi szary.
  2. Wyłącz „Pokaż godzinę” — karta się skurczy. Zmień w kodzie isVisible na tvGodzina.alpha = if (wlaczony) 1f else 0f. Godzina zniknie, ale miejsce zostanie.
  3. Zamień jeden MaterialSwitch w XML na <Switch> (i typ w kodzie). Zobaczysz, że wygląda inaczej niż pozostałe.
ELEMENTY WBUDOWANE

Zestawienie elementów

ElementTypCo robi
<com.google.android.material.materialswitch.MaterialSwitch>kontrolkaPrzełącznik Material 3.
android:checked, android:enabledatrybutyStan i aktywność na starcie.
sw.isCheckedBooleanWłączony czy wyłączony.
sw.setOnCheckedChangeListener { _, wlaczony -> }metodaKod przy każdej zmianie.
view.isVisibleBooleanWidoczność kontrolki (z miejscem). Import androidx.core.view.isVisible.
view.alphaFloatPrzezroczystość — miejsce zostaje.
if (…) a else bwyrażenieWybór jednej z dwóch wartości.
CZĘSTE BŁĘDY

Zanim utkniesz

Zapis / objawProblem
<MaterialSwitch> bez pełnej nazwyAwaria przy starcie: Error inflating class MaterialSwitch. Klasy spoza Androida wymagają pełnej ścieżki w XML.
findViewById<Switch> dla MaterialSwitchAwaria ClassCastException — to różne klasy. Typ w kodzie musi pasować do znacznika.
wyłączenie przełącznika nie przywraca wygląduListener obsługuje tylko wlaczony == true. Zawsze pisz gałąź else.
przełącznik z przyciskiem „Zapisz”Niezgodne z przyzwyczajeniami użytkowników — użyj CheckBox.
zależny przełącznik zostaje włączonyPrzy wyłączaniu nadrzędnego trzeba też ustawić isChecked = false zależnemu, nie tylko isEnabled.
ZADANIA

Zadania

Każde zadanie wykonaj w nowym projekcie Empty Views Activity, używając MaterialSwitch. Cały kod pisz w onCreate. Wskazówkę otwórz dopiero wtedy, gdy utkniesz.

ZAD 1Lampka★☆☆

Ikona żarówki (Vector Asset: lightbulb) i przełącznik „Światło”. Włączenie: ikona żółta, napis „Świeci”. Wyłączenie: ikona szara, napis „Zgaszone”.

pokaż wskazówkę
  • Kolor ikony z kodu: iv.setColorFilter(Color.parseColor("#FFC107")).
ZAD 2Pokaż hasło★☆☆

Pole hasła i przełącznik „Pokaż hasło”. Włączenie pokazuje wpisane znaki, wyłączenie ukrywa je z powrotem kropkami.

pokaż wskazówkę
  • etHaslo.inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD (pokazane) albo … or InputType.TYPE_TEXT_VARIATION_PASSWORD (ukryte). Po zmianie przestaw kursor na koniec: etHaslo.setSelection(etHaslo.text.length).
ZAD 3Panel inteligentnego domu★★☆

Pięć przełączników: światło w salonie, w kuchni, w sypialni, ogrzewanie, alarm. Na górze „Włączone: 2 z 5” i szacowane zużycie energii (światło 10 W, ogrzewanie 2000 W, alarm 5 W). Włączenie alarmu wyłącza wszystkie światła.

pokaż wskazówkę
  • Przełączniki w tablicy, liczenie w jednej funkcji. Wyłączanie świateł z kodu uruchomi ich listenery — dzięki temu licznik zaktualizuje się sam.
ZAD 4Przelicznik z przełącznikiem kierunku★★☆

Pole liczbowe, przełącznik „km → mile” (wyłączony: mile → km) i wynik przeliczany przy każdym przełączeniu i po przycisku. Podpis przełącznika zmienia się na aktualny kierunek.

pokaż wskazówkę
  • 1 mila = 1,609344 km. Podpis: sw.text = if (wlaczony) "km → mile" else "mile → km".
ZAD 5Tryb skupienia★★☆

Przełącznik główny „Tryb skupienia” i pod nim trzy zależne: „Wycisz powiadomienia”, „Ukryj gry”, „Szary ekran”. Zależne są aktywne tylko przy włączonym trybie. Włączenie trybu zaznacza wszystkie zależne, wyłączenie — odznacza i blokuje. „Szary ekran” zmienia tło całego ekranu na szare.

pokaż wskazówkę
  • To ten sam wzór co powiadomienia i dźwięk w przykładzie, tylko dla trzech przełączników naraz.
ZAD 6Rachunek z opcjami★★★

Kalkulator abonamentu telefonicznego: cena bazowa 35 zł i przełączniki usług: internet bez limitu (+20), roaming UE (+0, ale wymaga internetu — bez niego jest nieaktywny), muzyka (+15), ubezpieczenie telefonu (+12), faktura papierowa (+5), rabat studencki (−20% od sumy, aktywny dopiero przy cenie co najmniej 60 zł). Lista włączonych usług i cena przeliczają się po każdym przełączeniu.

pokaż wskazówkę
  • Rabat licz na końcu. Jeśli suma spadnie poniżej 60 zł, przełącznik rabatu trzeba wyłączyć i zablokować — uważaj, żeby zmiana z kodu nie wywoływała przeliczenia w nieskończoność (zmieniaj isChecked tylko wtedy, gdy rzeczywiście trzeba).
PODSUMOWANIE

Co trzeba zapamiętać

  • W projektach Material 3 używaj com.google.android.material.materialswitch.MaterialSwitch.
  • W kodzie przełącznik działa jak CheckBox: isChecked i setOnCheckedChangeListener.
  • Przełącznik oznacza zmianę natychmiastową — bez przycisku „Zapisz”.
  • W listenerze obsługuj oba stany: włączony i wyłączony.
  • Zależny przełącznik: isEnabled według nadrzędnego, a przy wyłączeniu nadrzędnego także isChecked = false.

Dokumentacja: Przełączniki w Androidzie, Material 3 — Switch.

Postęp zapisuje się w tej przeglądarce.