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

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.

Kotlin XML Activity Intent startActivity finish AndroidManifest.xml 2 × 45 min
CEL LEKCJI

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) i startActivity.
  • Zamkniesz ekran metodą finish().
  • Zrozumiesz stos ekranów i działanie systemowego przycisku Wstecz.
TEORIA

Ekran = aktywność

Każdy ekran aplikacji składa się z dwóch plików, które już znasz z MainActivity:

DzwonkiActivity.kt
kod ekranu
klasa dziedziczy po AppCompatActivity
+
activity_dzwonki.xml
wygląd ekranu
ładowany przez setContentView
+
wpis w manifeście
„ta aplikacja ma
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.

TEORIA

Tworzenie drugiej aktywności

  1. W panelu projektu kliknij prawym przyciskiem pakiet z MainActivity (np. pl.schoolit.informator).
  2. New → Activity → Empty Views Activity.
  3. Activity Name: DzwonkiActivity. Pole Layout Name uzupełni się samo: activity_dzwonki.
  4. 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.

TEORIA

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:

AndroidManifest.xml — fragment wewnątrz <application>
<!-- 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" />
ZapisZnaczenie
android:name=".DzwonkiActivity"Nazwa klasy. Kropka na początku = „w pakiecie aplikacji”.
MAIN + LAUNCHERTen 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.
TEORIA

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.

otwarcie drugiego ekranu
btnDzwonki.setOnClickListener {
    val intencja = Intent(this, DzwonkiActivity::class.java)   // skąd, dokąd
    startActivity(intencja)                                    // wyślij zamówienie
}
FragmentZnaczenie
Intent(…)Tworzy intencję. Wymaga importu android.content.Intent.
thisBieżąca aktywność — „skąd” wysyłamy.
DzwonkiActivity::class.javaKlasa 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.
TEORIA

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.

start
Main
→
startActivity(Dzwonki)
Dzwonki
Main
→
startActivity(Kontakt)
Kontakt
Dzwonki
Main
→
finish() lub Wstecz
Dzwonki
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().

PRZYKŁAD

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.

7:55
Szkolny informator
Technikum nr 1 · rok 2026/27
Dzwonki
Kontakt
Zamknij
MainActivity
→startActivity
7:55
Dzwonki
1.  8:00 –  8:45
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
Kontakt do sekretariatu
Wróć
DzwonkiActivity — stos: Dzwonki, Main
7:56
Kontakt
Sekretariat
tel. 59 000 00 00
sekretariat@szkola.example
Godziny pracy
pon.–pt. 7:30–15:30
Wróć
KontaktActivity — stos: Kontakt, Dzwonki, Main

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

  1. Nowy projekt Empty Views Activity, nazwa Informator, Kotlin, API 24.
  2. Kreatorem dodaj dwie aktywności: DzwonkiActivity i KontaktActivity (New → Activity → Empty Views Activity).
  3. Sprawdź manifest — powinien zawierać wpisy jak w sekcji „Wpis w manifeście”.
  4. Podmień zawartość trzech układów i trzech klas (linie package … zostaw ze swojego projektu).
app/src/main/
├── AndroidManifest.xmltrzy wpisy <activity>
├── java/…/
│   ├── MainActivity.ktmenu główne
│   ├── DzwonkiActivity.kt
│   └── KontaktActivity.kt
└── res/layout/
    ├── activity_main.xml
    ├── activity_dzwonki.xml
    └── activity_kontakt.xml
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: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>
MainActivity.kt — pełny plik
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
        }
    }
}
res/layout/activity_dzwonki.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: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>
DzwonkiActivity.kt — pełny plik
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")
    }
}
res/layout/activity_kontakt.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: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>
KontaktActivity.kt — pełny plik
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 — to MainActivity.
  • Dzwonki: intencja jawna → Android tworzy DzwonkiActivity, wywołuje jej onCreate, a ona ładuje swój układ activity_dzwonki.xml.
  • Każda aktywność ma własne kontrolki i własne initUI(). btnWroc w 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

  1. Otwórz Dzwonki → Kontakt i trzy razy użyj systemowego Wstecz. Zobaczysz kolejno: dzwonki, ekran główny, pulpit telefonu.
  2. W MainActivity po startActivity(intencja) dopisz finish(). Otwórz dzwonki i wciśnij Wstecz — aplikacja się zamknie, bo ekranu głównego już nie ma pod spodem. Usuń tę linię.
  3. Usuń z manifestu wpis KontaktActivity i kliknij Kontakt. Aplikacja się zamknie z błędem; w Logcat znajdziesz ActivityNotFoundException … have you declared this activity in your AndroidManifest.xml?. Przywróć wpis.
ELEMENTY WBUDOWANE

Zestawienie elementów

ElementTypCo robi
Intent(this, X::class.java)IntentIntencja jawna: otwórz ekran klasy X.
startActivity(intencja)metodaOtwiera ekran i kładzie go na wierzchu stosu.
finish()metodaZamyka bieżący ekran — widać ten pod spodem.
<activity android:name=".X" />manifestRejestruje ekran w aplikacji.
android:exported="false"atrybutEkran dostępny tylko dla naszej aplikacji.
intent-filter z MAIN i LAUNCHERmanifestEkran startowy z ikoną w telefonie.
New → Activity → Empty Views ActivitykreatorTworzy klasę, układ i wpis w manifeście.
android:fontFamily="monospace"atrybutCzcionka o stałej szerokości — kolumny godzin są równe.
CZĘSTE BŁĘDY

Zanim utkniesz

Zapis / objawProblem
aplikacja zamyka się po kliknięciu, Logcat: ActivityNotFoundExceptionBrak wpisu <activity> w manifeście — klasa utworzona ręcznie zamiast kreatorem.
drugi ekran wygląda jak pierwszyW 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 nullfindViewById 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 kompilacjiBrak ::class.java.
Unresolved reference: IntentBrak import android.content.Intent (Alt+Enter).
Wstecz z drugiego ekranu zamyka aplikacjęPo startActivity w ekranie głównym wywołano finish().
ZADANIA

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.

ZAD 1O aplikacji★☆☆

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().
ZAD 2Trzy przedmioty★☆☆

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.
ZAD 3Ekran powitalny★★☆

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 nim finish(). Tu to jest celowe.
ZAD 4Licznik ekranów★★☆

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 w onCreate.
ZAD 5Ekran zamknięcia z pytaniem★★☆

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.
ZAD 6Mini-quiz w trzech ekranach★★★

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 startActivity następnego woła finish(). Wtedy na stosie zostaje: start + bieżący ekran. Na początek to już tylko finish().
PODSUMOWANIE

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.

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