Button i TextView
Jak działają dwa najważniejsze widoki Androida – co można z nimi robić w kodzie, jak reagować na kliknięcia i jak poprawnie obsługiwać wiele przycisków jednocześnie.
Widoki – co to jest?
Wszystko co widzisz na ekranie aplikacji Android to widok (ang. View). Każdy widok to obiekt klasy dziedziczącej po bazowej klasie View z pakietu android.view.
Interfejs użytkownika aplikacji Android buduje się z widoków układanych w hierarchię. Widoki definiuje się w pliku XML (co opisuje wygląd), a ich zachowaniem steruje się w kodzie Java lub Kotlin (co opisuje logikę).
Kiedy kompilujesz projekt, Android Studio czyta wszystkie pliki XML i dla każdego atrybutu android:id="@+id/cokolwiek" generuje stałą liczbową w specjalnej klasie R. Dlatego R.id.tvWynik to po prostu liczba – unikalny numer identyfikujący widok. Metoda findViewById(R.id.tvWynik) przeszukuje układ i zwraca obiekt widoku o tym numerze. Bez android:id w XML – nie możemy odwołać się do widoku w kodzie.
| Widok | Do czego służy | Edytowalny przez użytkownika? |
|---|---|---|
TextView | Wyświetlanie tekstu na ekranie | ❌ Nie – tylko do odczytu |
Button | Przycisk reagujący na kliknięcie | ❌ Nie – ale rejestruje kliknięcia |
EditText | Pole do wpisywania tekstu przez użytkownika | ✅ Tak |
TextView – pole tekstowe
TextView wyświetla tekst. Użytkownik nie może go edytować – tylko programista może zmieniać go w kodzie. Służy do etykiet, wyników obliczeń, komunikatów, nagłówków.
Atrybuty XML – definiowanie wyglądu
<TextView android:id="@+id/tvWynik" <!-- ID do odwołania w kodzie --> android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Tekst domyślny" <!-- tekst widoczny od razu po uruchomieniu --> android:textSize="20sp" <!-- rozmiar – zawsze sp, nie px! --> android:textColor="#FFFFFF" <!-- kolor – zapis szesnastkowy RGB --> android:textStyle="bold" <!-- bold / italic / normal --> android:gravity="center" <!-- wyrównanie tekstu wewnątrz widoku --> android:background="#2D2D2D" <!-- kolor tła widoku --> android:padding="16dp" <!-- odstęp wewnętrzny od krawędzi --> android:layout_margin="8dp" <!-- odstęp zewnętrzny od sąsiednich widoków --> android:layout_marginBottom="24dp" <!-- można ustawić każdy bok osobno --> android:maxLines="3" <!-- maksymalna liczba linii --> android:ellipsize="end" /> <!-- "..." gdy tekst jest za długi -->
Rozmiar tekstu zawsze w sp (scale-independent pixels) – uwzględnia ustawienia rozmiaru czcionki w systemie telefonu. Wszystkie pozostałe wymiary (padding, margin, width, height) w dp (density-independent pixels). Zapis px nigdy nie powinien się pojawiać – piksele nie skalują się między urządzeniami.
Zmiana tekstu w kodzie
W XML ustalamy tekst domyślny. W kodzie możemy go zmienić w dowolnym momencie – np. po kliknięciu przycisku, po obliczeniu wyniku, po pobraniu danych.
public class MainActivity extends AppCompatActivity { TextView tvWynik; // pole klasy – dostępne we wszystkich metodach @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initUI(); } public void initUI() { tvWynik = findViewById(R.id.tvWynik); // Ustawienie nowego tekstu tvWynik.setText("Nowy tekst"); // Odczytanie aktualnego tekstu String aktualnyTekst = tvWynik.getText().toString(); // Składanie tekstu ze zmiennej int wynik = 42; tvWynik.setText("Wynik: " + wynik); // Ukrycie i pokazanie widoku tvWynik.setVisibility(View.GONE); // ukryj i zwolnij miejsce tvWynik.setVisibility(View.INVISIBLE); // ukryj, ale zostaw puste miejsce tvWynik.setVisibility(View.VISIBLE); // pokaż widok } }
class MainActivity : AppCompatActivity() { lateinit var tvWynik: TextView override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) initUI() } fun initUI() { tvWynik = findViewById(R.id.tvWynik) // Kotlin: właściwość .text zamiast setText() / getText() tvWynik.text = "Nowy tekst" // Odczytanie aktualnego tekstu val aktualnyTekst = tvWynik.text.toString() // String template – $ wstawia wartość zmiennej val wynik = 42 tvWynik.text = "Wynik: $wynik" // Widoczność tvWynik.visibility = View.GONE tvWynik.visibility = View.VISIBLE } }
| Wartość visibility | Widoczny? | Zajmuje miejsce? |
|---|---|---|
View.VISIBLE | ✅ Tak | ✅ Tak |
View.INVISIBLE | ❌ Nie | ✅ Tak – widok jest niewidoczny ale „trzyma” swoje miejsce |
View.GONE | ❌ Nie | ❌ Nie – jakby widok nie istniał, inne widoki zajmują jego miejsce |
Zmiana wyglądu w kodzie
Wszystkie atrybuty które ustawiłeś w XML możesz też ustawić lub zmienić w kodzie – na przykład w odpowiedzi na kliknięcie przycisku.
// Zmiana koloru tekstu tvWynik.setTextColor(Color.RED); // stała koloru tvWynik.setTextColor(Color.parseColor("#FF5733")); // hex string tvWynik.setTextColor(getColor(R.color.colorPrimary)); // z zasobów colors.xml // Zmiana rozmiaru tekstu (w sp) tvWynik.setTextSize(24f); // domyślnie w sp // Zmiana koloru tła tvWynik.setBackgroundColor(Color.parseColor("#2ECC71")); // Pogrubienie / kursywa – Typeface tvWynik.setTypeface(null, Typeface.BOLD); tvWynik.setTypeface(null, Typeface.ITALIC); tvWynik.setTypeface(null, Typeface.NORMAL);
// Zmiana koloru tekstu tvWynik.setTextColor(Color.RED) tvWynik.setTextColor(Color.parseColor("#FF5733")) tvWynik.setTextColor(getColor(R.color.colorPrimary)) // Zmiana rozmiaru tekstu tvWynik.setTextSize(24f) // Zmiana koloru tła tvWynik.setBackgroundColor(Color.parseColor("#2ECC71")) // Pogrubienie / kursywa tvWynik.setTypeface(null, Typeface.BOLD) tvWynik.setTypeface(null, Typeface.NORMAL)
Zmiana koloru tekstu lub tła w kodzie przydaje się np. do sygnalizowania statusu: odpowiedź poprawna → tekst zielony, błędna → czerwony. Nie trzeba tworzyć dwóch osobnych widoków – wystarczy jeden TextView którego kolor zmieniamy w zależności od wyniku.
Button – przycisk
Button to klasa dziedzicząca po TextView – dlatego ma wszystkie atrybuty TextView (tekst, rozmiar, kolor) plus własne, związane z klikalnością.
Atrybuty XML
<Button android:id="@+id/btnZapisz" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Zapisz dane" android:textSize="16sp" android:backgroundTint="#388BFD" <!-- kolor tła Material Button --> android:textColor="#FFFFFF" android:paddingStart="24dp" android:paddingEnd="24dp" android:layout_marginTop="16dp" android:enabled="true" /> <!-- false = przycisk nieaktywny (szary) -->
Ponieważ Button extends TextView, wszystkie metody które działają na TextView działają też na Button: setText(), setTextColor(), setVisibility(). Możesz zmieniać tekst przycisku w trakcie działania aplikacji – np. zmienić „Zatwierdź” na „Zmień” po pierwszym kliknięciu.
Jak działa kliknięcie – mechanizm
Gdy użytkownik dotyka ekranu, system Android generuje zdarzenie (ang. event). Zdarzenie wędruje przez hierarchię widoków aż do widoku który je obsługuje. Jeśli Button ma zarejestrowany nasłuchiwacz zdarzeń (ang. listener), Android wywołuje jego metodę onClick().
Listener rejestrujemy metodą setOnClickListener(). Bez rejestracji listenera kliknięcie przycisku nic nie robi.
setOnClickListener – rejestracja nasłuchiwacza
setOnClickListener() to metoda która rejestruje obiekt nasłuchujący kliknięć. Przekazujesz jej implementację interfejsu View.OnClickListener – w praktyce dziś robi się to przez lambdę. To podstawowy i zalecany sposób obsługi kliknięć.
Jeden przycisk – pełny przykład
Tworzymy aplikację: użytkownik klika przycisk, w polu TextView pojawia się tekst. Pola widoków deklarujemy na poziomie klasy, a inicjalizację przeprowadzamy w metodzie initUI().
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:gravity="center_horizontal" android:padding="24dp"> <TextView android:id="@+id/tvWynik" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Naciśnij przycisk..." android:textSize="22sp" android:gravity="center" android:padding="20dp" android:layout_marginBottom="32dp" /> <Button android:id="@+id/btnKliknij" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Kliknij mnie" /> <!-- Brak android:onClick – obsługę kliknięcia ustawiamy w kodzie --> </LinearLayout>
public class MainActivity extends AppCompatActivity { // Pola klasy – dostępne we wszystkich metodach, nie tylko w onCreate TextView tvWynik; Button btnKliknij; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initUI(); // inicjalizacja widoków w osobnej metodzie – porządek w kodzie } public void initUI() { // Znajdź widoki w layoucie po ich android:id tvWynik = findViewById(R.id.tvWynik); btnKliknij = findViewById(R.id.btnKliknij); // Zarejestruj listener – kod w lambdzie wykona się po kliknięciu btnKliknij.setOnClickListener(v -> { tvWynik.setText("Przycisk kliknięty!"); }); } }
class MainActivity : AppCompatActivity() { lateinit var tvWynik : TextView lateinit var btnKliknij: Button override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) initUI() } fun initUI() { tvWynik = findViewById(R.id.tvWynik) btnKliknij = findViewById(R.id.btnKliknij) // Kotlin: lambda bez nawiasów poza setOnClickListener btnKliknij.setOnClickListener { tvWynik.text = "Przycisk kliknięty!" } } }
Deklarując TextView tvWynik; jako pole klasy, masz dostęp do tego widoku w każdej metodzie – nie tylko w onCreate(). Gdybyś zadeklarował go wewnątrz onCreate() jako zmienną lokalną, byłby niedostępny w innych metodach. Metoda initUI() to dobry zwyczaj: skupia całą inicjalizację widoków w jednym miejscu – kod jest przejrzystszy.
Wiele przycisków – jeden listener z v.getId()
Gdy mamy kilka przycisków, możemy stworzyć jeden listener i przypisać go do wszystkich. Parametr View v to referencja do widoku który kliknięto – wywołując v.getId() dostajemy jego identyfikator.
<TextView android:id="@+id/tvWynik" android:textSize="24sp" android:text="Wynik: 0" android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center" android:padding="16dp" android:layout_marginBottom="24dp" /> <Button android:id="@+id/btnDodaj" android:text="Dodaj (+1)" android:layout_width="match_parent" android:layout_height="wrap_content" /> <Button android:id="@+id/btnOdejmij" android:text="Odejmij (−1)" android:layout_width="match_parent" android:layout_height="wrap_content" /> <Button android:id="@+id/btnCzysc" android:text="Wyczyść" android:layout_width="match_parent" android:layout_height="wrap_content" />
public class MainActivity extends AppCompatActivity { TextView tvWynik; Button btnDodaj, btnOdejmij, btnCzysc; int wynik = 0; // pole klasy – zachowuje wartość między kliknięciami @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initUI(); } public void initUI() { tvWynik = findViewById(R.id.tvWynik); btnDodaj = findViewById(R.id.btnDodaj); btnOdejmij = findViewById(R.id.btnOdejmij); btnCzysc = findViewById(R.id.btnCzysc); // Listener zapisany w zmiennej – możemy przypisać go do wielu przycisków View.OnClickListener listener = v -> { // v.getId() – identyfikator KLIKNIĘTEGO widoku switch (v.getId()) { case R.id.btnDodaj: wynik++; break; case R.id.btnOdejmij: wynik--; break; case R.id.btnCzysc: wynik = 0; break; } tvWynik.setText("Wynik: " + wynik); }; // Przypisz TEN SAM listener do wszystkich trzech przycisków btnDodaj .setOnClickListener(listener); btnOdejmij.setOnClickListener(listener); btnCzysc .setOnClickListener(listener); } }
class MainActivity : AppCompatActivity() { lateinit var tvWynik : TextView lateinit var btnDodaj : Button lateinit var btnOdejmij : Button lateinit var btnCzysc : Button var wynik = 0 override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) initUI() } fun initUI() { tvWynik = findViewById(R.id.tvWynik) btnDodaj = findViewById(R.id.btnDodaj) btnOdejmij = findViewById(R.id.btnOdejmij) btnCzysc = findViewById(R.id.btnCzysc) val listener = View.OnClickListener { v -> when (v.id) { R.id.btnDodaj -> wynik++ R.id.btnOdejmij -> wynik-- R.id.btnCzysc -> wynik = 0 } tvWynik.text = "Wynik: $wynik" } btnDodaj .setOnClickListener(listener) btnOdejmij.setOnClickListener(listener) btnCzysc .setOnClickListener(listener) } }
android:onClick w XML – kiedy NIE używać
W starszych tutorialach i podręcznikach często pojawia się android:onClick="nazwaMetody" – atrybut który każe Androidowi wywołać metodę po kliknięciu. To wygodne skrócie, ale ma ważne ograniczenia których trzeba być świadomym.
Wpisujesz w XML: android:onClick="kliknij". Android po kliknięciu szuka metody kliknij w bieżącej Activity. Metoda musi być public void kliknij(View v). Jeśli jej nie znajdzie – crash w czasie działania.
<Button android:id="@+id/btnKliknij" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Kliknij" android:onClick="kliknij" /> <!-- szuka metody kliknij() w Activity -->
// Metoda MUSI być: public, void, z parametrem View v public void kliknij(View v) { tvWynik.setText("Kliknięto!"); }
// Metoda MUSI być widoczna z Javy (fun, nie private), zwracać Unit, mieć View fun kliknij(v: View) { tvWynik.text = "Kliknięto!" }
Poniżej zestawienie ograniczeń które sprawiają że android:onClick nie powinien być podstawowym wyborem:
| Sytuacja | android:onClick | setOnClickListener |
|---|---|---|
| Używasz w Activity | ✅ Działa | ✅ Działa |
| Używasz w Fragment | ❌ Crash – szuka metody w Activity, nie w Fragmencie | ✅ Działa |
| Przycisk tworzony dynamicznie w kodzie (nie z XML) | ❌ Niemożliwe – nie ma XML | ✅ Jedyna opcja |
| Błąd w nazwie metody | ❌ Crash w czasie działania – trudno znaleźć | ✅ Błąd kompilacji – łatwo znaleźć |
| Czytelność kodu | ⚠️ Logika „ukryta” w XML | ✅ Wszystko widoczne w kodzie |
| Refaktoryzacja (zmiana nazwy) | ⚠️ Trzeba pamiętać o zmianie w XML | ✅ IDE zmienia automatycznie |
android:onClick jest szybki na szybkie prototypy i proste ćwiczenia w Activity. Jeśli piszesz „prawdziwy” kod, pracujesz z Fragmentami lub tworzysz widoki dynamicznie – zawsze używaj setOnClickListener. To bezpieczniejszy, bardziej elastyczny i zalecany przez Google sposób.
setOnLongClickListener – przytrzymanie
setOnLongClickListener działa tak samo jak setOnClickListener, ale reaguje na przytrzymanie widoku (ok. 1 sekundy). Używa się go do akcji „destrukcyjnych” lub wymagających potwierdzenia – np. usunięcie elementu, reset licznika.
onLongClick() musi zwrócić true lub false. Zwrócenie true oznacza: „obsłużyłem zdarzenie – koniec, nie wywołuj nic więcej”. Zwrócenie false oznacza: „obsłużyłem długie kliknięcie, ale potem wywołaj też normalne kliknięcie (onClick)”. W 99% przypadków zwracamy true.
public void initUI() { tvWynik = findViewById(R.id.tvWynik); btnCzysc = findViewById(R.id.btnCzysc); // Krótkie kliknięcie – normalny reset btnCzysc.setOnClickListener(v -> { wynik = 0; tvWynik.setText("Wynik: 0"); }); // Przytrzymanie – inny efekt, np. zmiana koloru + reset btnCzysc.setOnLongClickListener(v -> { wynik = 0; tvWynik.setText("Zresetowano!"); tvWynik.setTextColor(Color.RED); return true; // true = nie wywołuj onClick po tym }); }
fun initUI() { tvWynik = findViewById(R.id.tvWynik) btnCzysc = findViewById(R.id.btnCzysc) // Krótkie kliknięcie btnCzysc.setOnClickListener { wynik = 0 tvWynik.text = "Wynik: 0" } // Przytrzymanie – ostatnia linia lambdy to zwracana wartość (true) btnCzysc.setOnLongClickListener { wynik = 0 tvWynik.text = "Zresetowano!" tvWynik.setTextColor(Color.RED) true // nie wywołuj onClick po tym } }
LongClick na wielu przyciskach – identyczny wzorzec co onClick: jeden listener z switch / when na v.getId():
View.OnLongClickListener longListener = v -> { switch (v.getId()) { case R.id.btnDodaj: tvWynik.setText("Przytrzymano: DODAJ"); break; case R.id.btnOdejmij: tvWynik.setText("Przytrzymano: ODEJMIJ"); break; } return true; }; btnDodaj .setOnLongClickListener(longListener); btnOdejmij.setOnLongClickListener(longListener);
val longListener = View.OnLongClickListener { v -> when (v.id) { R.id.btnDodaj -> tvWynik.text = "Przytrzymano: DODAJ" R.id.btnOdejmij -> tvWynik.text = "Przytrzymano: ODEJMIJ" } true } btnDodaj .setOnLongClickListener(longListener) btnOdejmij.setOnLongClickListener(longListener)
Podsumowanie
| Operacja | Java | Kotlin |
|---|---|---|
| Znajdź widok | tv = findViewById(R.id.tv); |
tv = findViewById(R.id.tv) |
| Ustaw tekst | tv.setText("napis"); |
tv.text = "napis" |
| Odczytaj tekst | tv.getText().toString() |
tv.text.toString() |
| Kolor tekstu | tv.setTextColor(Color.RED); |
tv.setTextColor(Color.RED) |
| Kolor tła | tv.setBackgroundColor(Color.parseColor("#hex")); |
tv.setBackgroundColor(Color.parseColor("#hex")) |
| Widoczność | tv.setVisibility(View.GONE); |
tv.visibility = View.GONE |
| Listener (jeden) | btn.setOnClickListener(v -> { }); |
btn.setOnClickListener { } |
| Który przycisk? | switch(v.getId()) { case R.id.x: ... } |
when(v.id) { R.id.x -> ... } |
| Long click | btn.setOnLongClickListener(v -> { return true; }); |
btn.setOnLongClickListener { true } |
Zadanie dla uczniów
Prosta zgadywanka
- Stwórz layout z
TextViewwyświetlającym pytanie: „Ile to 7 × 8?” i trzema przyciskami: 54, 56, 64 - Zadeklaruj widoki jako pola klasy, zainicjalizuj w metodzie
initUI() - Użyj
setOnClickListenerz jednym wspólnym listenerem iswitch/whennav.getId() - Poprawna odpowiedź (56):
TextViewwyświetla „✅ Poprawnie!” i zmienia kolor tekstu na zielony (Color.parseColor("#2ECC71")) - Błędna odpowiedź: „❌ Błąd, spróbuj ponownie” i kolor tekstu na czerwony
- Dodaj czwarty przycisk Reset który przywraca pierwotne pytanie i domyślny kolor tekstu
⭐ Bonus 1: do przycisku Reset dodaj setOnLongClickListener – przy przytrzymaniu tekst zmienia się na „Naprawdę resetujesz?” przez 2 sekundy
⭐⭐ Bonus 2: dodaj licznik prób – TextView wyświetla „Próba: X” po każdym kliknięciu odpowiedzi
⭐⭐⭐ Bonus Kotlin: przepisz całość używając właściwości zamiast setterów (.text, .visibility) i string templates ("Próba: $licznik")