school-it.pl // android + kotlin // rozdział 8 // lekcja 30
Intencja prosta — drugi ekran
Do tej pory każda aplikacja miała jeden ekran. Prawdziwe aplikacje mają ich wiele: menu, szczegóły, ustawienia. W Androidzie każdy ekran to osobna aktywność, a przejście między nimi zamawia się intencją (Intent) — kartką z napisem „otwórz ten ekran”. Zbudujesz szkolny informator z trzema ekranami i zobaczysz, jak Android układa je w stos.
Czego się dziś nauczysz
- Dodasz do projektu drugą i trzecią aktywność kreatorem Android Studio.
- Sprawdzisz wpis aktywności w
AndroidManifest.xml. - Otworzysz nowy ekran intencją:
Intent(this, X::class.java)istartActivity. - Zamkniesz ekran metodą
finish(). - Zrozumiesz stos ekranów i działanie systemowego przycisku Wstecz.
Ekran = aktywność
Każdy ekran aplikacji składa się z dwóch plików, które już znasz z MainActivity:
klasa dziedziczy po AppCompatActivity
ładowany przez setContentView
taki ekran”
Nazwy trzymają się zasady: klasa CośActivity, układ activity_cos.xml. Dzięki temu w większym projekcie od razu widać, który plik należy do którego ekranu.
Tworzenie drugiej aktywności
- W panelu projektu kliknij prawym przyciskiem pakiet z
MainActivity(np.pl.schoolit.informator). - New → Activity → Empty Views Activity.
- Activity Name:
DzwonkiActivity. Pole Layout Name uzupełni się samo:activity_dzwonki. - Nie zaznaczaj Launcher Activity — ekran startowy już jest. Kliknij Finish.
Kreator zrobi trzy rzeczy naraz: utworzy klasę, utworzy układ i dopisze aktywność do manifestu. W wygenerowanej klasie będą też linie enableEdgeToEdge() i ViewCompat.setOnApplyWindowInsetsListener — w tym kursie zastępujemy je atrybutem android:fitsSystemWindows="true" w układzie, więc całą klasę podmieniasz na kod z lekcji.
Wpis w manifeście
AndroidManifest.xml to spis treści aplikacji. Android otworzy tylko taki ekran, który jest w nim zapisany. Po użyciu kreatora fragment <application> wygląda tak:
<!-- ekran startowy: ma filtr MAIN + LAUNCHER, więc ma ikonę w telefonie -->
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<!-- kolejne ekrany: otwierane tylko z wnętrza aplikacji -->
<activity
android:name=".DzwonkiActivity"
android:exported="false" />
<activity
android:name=".KontaktActivity"
android:exported="false" />
| Zapis | Znaczenie |
|---|---|
android:name=".DzwonkiActivity" | Nazwa klasy. Kropka na początku = „w pakiecie aplikacji”. |
MAIN + LAUNCHER | Ten ekran otwiera się po dotknięciu ikony aplikacji. Ma go tylko jedna aktywność. |
android:exported="false" | Inne aplikacje nie mogą otworzyć tego ekranu — tylko nasza. |
Intencja i startActivity
Intencja to zamówienie wysłane do systemu: „otwórz ekran DzwonkiActivity”. Taka intencja, w której wprost podajesz klasę ekranu, nazywa się jawna.
btnDzwonki.setOnClickListener {
val intencja = Intent(this, DzwonkiActivity::class.java) // skąd, dokąd
startActivity(intencja) // wyślij zamówienie
}
| Fragment | Znaczenie |
|---|---|
Intent(…) | Tworzy intencję. Wymaga importu android.content.Intent. |
this | Bieżąca aktywność — „skąd” wysyłamy. |
DzwonkiActivity::class.java | Klasa ekranu docelowego — „dokąd”. ::class wskazuje klasę, a .java podaje ją w postaci, której oczekuje Android. |
startActivity(intencja) | Android tworzy nowy ekran, wywołuje jego onCreate i kładzie go na wierzchu. |
Stos ekranów i finish()
Otwarte ekrany leżą na sobie jak talerze: nowy kładzie się na wierzch, a widać tylko ten na górze. Poprzednie nie znikają — czekają pod spodem.
Main
Dzwonki
Main
Main
pogrubiony ekran jest na wierzchu — to go widzi użytkownik
finish() zdejmuje ze stosu bieżący ekran. Systemowy przycisk (lub gest) Wstecz robi to samo. Gdy zdejmiesz ostatni ekran — aplikacja się zamyka.
Nie otwieraj poprzedniego ekranu intencją
Przycisk „Wróć” w DzwonkiActivity nie powinien robić startActivity(Intent(this, MainActivity::class.java)). To położy na stosie drugi ekran główny: Main → Dzwonki → Main. Po kilku takich powrotach Wstecz trzeba wciskać coraz więcej razy. Wracamy zawsze przez finish().
Szkolny informator
Ekran główny ma dwa przyciski: Dzwonki i Kontakt. Ekran dzwonków pokazuje godziny lekcji i może otworzyć kontakt — wtedy na stosie leżą trzy ekrany. Każdy ekran ma przycisk Wróć, a ekran główny — Zamknij.
2. 8:50 – 9:35
3. 9:45 – 10:30
4. 10:40 – 11:25
5. 11:45 – 12:30
6. 12:40 – 13:25
7. 13:30 – 14:15
8. 14:20 – 15:05
trzeci ekran otwiera przycisk „Kontakt do sekretariatu”; „Wróć” w kontakcie wraca do dzwonków, a nie do ekranu głównego — bo dzwonki leżą pod spodem
Przygotowanie projektu
- Nowy projekt Empty Views Activity, nazwa
Informator, Kotlin, API 24. - Kreatorem dodaj dwie aktywności:
DzwonkiActivityiKontaktActivity(New → Activity → Empty Views Activity). - Sprawdź manifest — powinien zawierać wpisy jak w sekcji „Wpis w manifeście”.
- Podmień zawartość trzech układów i trzech klas (linie
package …zostaw ze swojego projektu).
├── AndroidManifest.xmltrzy wpisy <activity>
├── java/…/
│ ├── MainActivity.ktmenu główne
│ ├── DzwonkiActivity.kt
│ └── KontaktActivity.kt
└── res/layout/
├── activity_main.xml
├── activity_dzwonki.xml
└── activity_kontakt.xml
<?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:layout_marginTop="16dp"
android:text="Szkolny informator"
android:textSize="28sp"
android:textStyle="bold" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="32dp"
android:text="Technikum nr 1 · rok 2026/27" />
<Button
android:id="@+id/btnDzwonki"
android:layout_width="match_parent"
android:layout_height="64dp"
android:text="Dzwonki"
android:textSize="18sp" />
<Button
android:id="@+id/btnKontakt"
android:layout_width="match_parent"
android:layout_height="64dp"
android:layout_marginTop="8dp"
android:text="Kontakt"
android:textSize="18sp" />
<Button
android:id="@+id/btnZamknij"
style="@style/Widget.Material3.Button.OutlinedButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:text="Zamknij" />
</LinearLayout>
package pl.schoolit.informator
import android.content.Intent
import android.os.Bundle
import android.widget.Button
import androidx.appcompat.app.AppCompatActivity
class MainActivity : AppCompatActivity() {
// ---------- kontrolki ----------
private lateinit var btnDzwonki: Button
private lateinit var btnKontakt: Button
private lateinit var btnZamknij: Button
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
initUI()
ustawZdarzenia()
}
private fun initUI() {
btnDzwonki = findViewById(R.id.btnDzwonki)
btnKontakt = findViewById(R.id.btnKontakt)
btnZamknij = findViewById(R.id.btnZamknij)
}
private fun ustawZdarzenia() {
btnDzwonki.setOnClickListener {
val intencja = Intent(this, DzwonkiActivity::class.java)
startActivity(intencja)
}
btnKontakt.setOnClickListener {
startActivity(Intent(this, KontaktActivity::class.java)) // to samo, w jednej linii
}
btnZamknij.setOnClickListener {
finish() // ostatni ekran na stosie — aplikacja się zamyka
}
}
}
<?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:layout_marginTop="16dp"
android:text="Dzwonki"
android:textSize="28sp"
android:textStyle="bold" />
<TextView
android:id="@+id/tvDzwonki"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:fontFamily="monospace"
android:lineSpacingExtra="6dp"
android:textSize="18sp" />
<Button
android:id="@+id/btnKontakt"
style="@style/Widget.Material3.Button.OutlinedButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:text="Kontakt do sekretariatu" />
<Button
android:id="@+id/btnWroc"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Wróć" />
</LinearLayout>
package pl.schoolit.informator
import android.content.Intent
import android.os.Bundle
import android.widget.Button
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
class DzwonkiActivity : AppCompatActivity() {
// ---------- kontrolki ----------
private lateinit var tvDzwonki: TextView
private lateinit var btnKontakt: Button
private lateinit var btnWroc: Button
// ---------- dane ----------
private val godziny = arrayOf(
" 8:00 – 8:45", " 8:50 – 9:35", " 9:45 – 10:30", "10:40 – 11:25",
"11:45 – 12:30", "12:40 – 13:25", "13:30 – 14:15", "14:20 – 15:05"
)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_dzwonki) // WŁASNY układ, nie activity_main
initUI()
ustawZdarzenia()
pokazDzwonki()
}
private fun initUI() {
tvDzwonki = findViewById(R.id.tvDzwonki)
btnKontakt = findViewById(R.id.btnKontakt)
btnWroc = findViewById(R.id.btnWroc)
}
private fun ustawZdarzenia() {
btnKontakt.setOnClickListener {
startActivity(Intent(this, KontaktActivity::class.java)) // trzeci ekran na stosie
}
btnWroc.setOnClickListener {
finish() // zdejmij ten ekran ze stosu
}
}
private fun pokazDzwonki() {
// "1. 8:00 – 8:45" itd., każda lekcja w osobnej linii
tvDzwonki.text = godziny
.mapIndexed { i, g -> "${i + 1}. $g" }
.joinToString("\n")
}
}
<?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:layout_marginTop="16dp"
android:text="Kontakt"
android:textSize="28sp"
android:textStyle="bold" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:text="Sekretariat"
android:textSize="18sp"
android:textStyle="bold" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="tel. 59 000 00 00\nsekretariat@szkola.example"
android:textSize="16sp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:text="Godziny pracy"
android:textSize="18sp"
android:textStyle="bold" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="pon.–pt. 7:30–15:30"
android:textSize="16sp" />
<Button
android:id="@+id/btnWroc"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:text="Wróć" />
</LinearLayout>
package pl.schoolit.informator
import android.os.Bundle
import android.widget.Button
import androidx.appcompat.app.AppCompatActivity
class KontaktActivity : AppCompatActivity() {
private lateinit var btnWroc: Button
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_kontakt)
initUI()
btnWroc.setOnClickListener { finish() }
}
private fun initUI() {
btnWroc = findViewById(R.id.btnWroc)
}
}
Co dzieje się po kolei
- Po dotknięciu ikony Android szuka w manifeście aktywności z filtrem
LAUNCHER— toMainActivity. - Dzwonki: intencja jawna → Android tworzy
DzwonkiActivity, wywołuje jejonCreate, a ona ładuje swój układactivity_dzwonki.xml. - Każda aktywność ma własne kontrolki i własne
initUI().btnWrocw dwóch klasach to dwa różne przyciski z dwóch różnych układów — mogą mieć to samo id. - Kontakt do sekretariatu kładzie trzeci ekran na stos. Wróć (
finish()) zdejmuje go i znów widać dzwonki. - Zamknij na ekranie głównym:
finish()ostatniego ekranu zamyka aplikację.
Sprawdź trzy rzeczy
- Otwórz Dzwonki → Kontakt i trzy razy użyj systemowego Wstecz. Zobaczysz kolejno: dzwonki, ekran główny, pulpit telefonu.
- W
MainActivitypostartActivity(intencja)dopiszfinish(). Otwórz dzwonki i wciśnij Wstecz — aplikacja się zamknie, bo ekranu głównego już nie ma pod spodem. Usuń tę linię. - Usuń z manifestu wpis
KontaktActivityi kliknij Kontakt. Aplikacja się zamknie z błędem; w Logcat znajdzieszActivityNotFoundException … have you declared this activity in your AndroidManifest.xml?. Przywróć wpis.
Zestawienie elementów
| Element | Typ | Co robi |
|---|---|---|
Intent(this, X::class.java) | Intent | Intencja jawna: otwórz ekran klasy X. |
startActivity(intencja) | metoda | Otwiera ekran i kładzie go na wierzchu stosu. |
finish() | metoda | Zamyka bieżący ekran — widać ten pod spodem. |
<activity android:name=".X" /> | manifest | Rejestruje ekran w aplikacji. |
android:exported="false" | atrybut | Ekran dostępny tylko dla naszej aplikacji. |
intent-filter z MAIN i LAUNCHER | manifest | Ekran startowy z ikoną w telefonie. |
| New → Activity → Empty Views Activity | kreator | Tworzy klasę, układ i wpis w manifeście. |
android:fontFamily="monospace" | atrybut | Czcionka o stałej szerokości — kolumny godzin są równe. |
Zanim utkniesz
| Zapis / objaw | Problem |
|---|---|
aplikacja zamyka się po kliknięciu, Logcat: ActivityNotFoundException | Brak wpisu <activity> w manifeście — klasa utworzona ręcznie zamiast kreatorem. |
| drugi ekran wygląda jak pierwszy | W nowej aktywności setContentView(R.layout.activity_main) — skopiowany kod. Każdy ekran ładuje swój układ. |
awaria w drugim ekranie: NullPointerException: findViewById(…) must not be null | findViewById szuka id, którego nie ma w załadowanym układzie — zły układ w setContentView albo literówka w id. |
„Wróć” robi startActivity(… MainActivity …) | Na stosie mnożą się ekrany główne. Wracaj przez finish(). |
Intent(this, DzwonkiActivity) → błąd kompilacji | Brak ::class.java. |
Unresolved reference: Intent | Brak import android.content.Intent (Alt+Enter). |
| Wstecz z drugiego ekranu zamyka aplikację | Po startActivity w ekranie głównym wywołano finish(). |
Zadania
Każde zadanie wykonaj w nowym projekcie Empty Views Activity. Nowe ekrany twórz kreatorem; w każdej aktywności pola klasy i initUI(). Wskazówkę otwórz dopiero wtedy, gdy utkniesz.
Ekran główny z przyciskiem O aplikacji. Drugi ekran: nazwa aplikacji, wersja, autor i przycisk OK, który wraca.
pokaż wskazówkę
- Kreator →
OAplikacjiActivity; przycisk OK:finish().
Ekran główny z trzema przyciskami: Matematyka, Fizyka, Informatyka. Każdy otwiera osobny ekran z nazwą przedmiotu (duży napis), nazwiskiem nauczyciela i salą. Na każdym ekranie przycisk Wróć.
pokaż wskazówkę
- Trzy aktywności, trzy układy. W lekcji 31 zobaczysz, jak zrobić to jednym ekranem z różnymi danymi.
Pierwszy ekran wita użytkownika logo i przyciskiem Zaczynamy. Kliknięcie otwiera ekran główny i zamyka powitanie — Wstecz z ekranu głównego ma zamknąć aplikację, a nie wracać do powitania.
pokaż wskazówkę
- W powitaniu:
startActivity(…), a zaraz po nimfinish(). Tu to jest celowe.
Ekran PoziomActivity z napisem „Poziom” i dwoma przyciskami: Głębiej (otwiera kolejną kopię tego samego ekranu) i Wyżej (finish()). Otwórz 5 poziomów i wracaj — przekonaj się, że każdy jest osobnym ekranem na stosie. Przy każdym otwarciu pokaż Toast „Nowy ekran”.
pokaż wskazówkę
- W
PoziomActivity:startActivity(Intent(this, PoziomActivity::class.java)). Toast wonCreate.
Rozbuduj przykład: Zamknij na ekranie głównym pyta AlertDialogiem „Zamknąć informator?” i dopiero po Tak wywołuje finish().
pokaż wskazówkę
.setPositiveButton("Tak") { _, _ -> finish() }— lekcja 22.
Ekran startowy z przyciskiem Start → ekran pytania 1 (RadioGroup, przycisk Dalej) → ekran pytania 2 → ekran końca „Dziękujemy!” z przyciskiem Na początek. Na końcu Wstecz ani Na początek nie może prowadzić z powrotem do pytań.
pokaż wskazówkę
- Każdy ekran pytania po
startActivitynastępnego wołafinish(). Wtedy na stosie zostaje: start + bieżący ekran. Na początek to już tylkofinish().
Co trzeba zapamiętać
- Ekran = klasa aktywności + układ XML + wpis w manifeście. Kreator robi wszystkie trzy.
- Otwarcie ekranu:
startActivity(Intent(this, X::class.java)). - Ekrany leżą na stosie; widać ten na górze.
- Powrót:
finish()albo systemowe Wstecz — nigdy nowa intencja do poprzedniego ekranu. - Brak wpisu w manifeście =
ActivityNotFoundException.
W następnej lekcji drugi ekran dostanie dane z pierwszego.
Dokumentacja: Wprowadzenie do aktywności, Zadania i stos wstecz.