school-it.pl // android + kotlin // rozdział 9 // lekcja 35
Menu na pasku aplikacji
Przyciski zajmują miejsce na ekranie. Czynności, z których korzysta się rzadziej — ustawienia, udostępnianie, „O aplikacji” — chowa się w menu na górnym pasku. Najważniejsze dostają ikonę na pasku, reszta trafia pod trzy kropki ⋮. Dodasz do aplikacji pasek (MaterialToolbar), opiszesz menu w pliku XML i obsłużysz wybór pozycji. Zbudujesz notatnik ze zmianą wielkości czcionki, trybem nocnym i udostępnianiem.
Czego się dziś nauczysz
- Dodasz pasek aplikacji
MaterialToolbari podłączysz go jako pasek akcji. - Utworzysz plik menu w
res/menuz ikonami i pozycjami ukrytymi pod ⋮. - Pokażesz menu w
onCreateOptionsMenu. - Obsłużysz wybór pozycji w
onOptionsItemSelected. - Użyjesz pozycji z zaznaczeniem (włącz / wyłącz) i podtytułu paska.
Pasek aplikacji
Szablon Empty Views Activity używa motywu bez paska (…NoActionBar) — dlatego dotąd na górze ekranu nic nie było. Pasek wstawiamy sami jako zwykłą kontrolkę w układzie i mówimy aktywności: „to jest twój pasek akcji”.
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimaryContainer"
app:title="Notatnik" />
setSupportActionBar(toolbar) // od teraz menu i tytuł trafiają na ten pasek
| Zapis | Znaczenie |
|---|---|
?attr/actionBarSize | Standardowa wysokość paska z motywu (zwykle 56–64dp). |
?attr/colorPrimaryContainer | Kolor z motywu Material 3 — pasek pasuje do reszty aplikacji. |
app:title | Tytuł na pasku. Atrybut z przestrzeni app: — w głównym elemencie układu musi być xmlns:app="http://schemas.android.com/apk/res-auto". |
supportActionBar?.subtitle = "…" | Podtytuł pod tytułem — zmieniany z kodu. |
Plik menu
Pozycje menu opisuje się w XML, w osobnym folderze res/menu. Tworzysz go: prawy klik na res → New → Android Resource File → Resource type: Menu, nazwa pliku menu_main.
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<!-- ikona na pasku, jeśli jest miejsce -->
<item
android:id="@+id/akcja_powieksz"
android:icon="@drawable/ic_powieksz"
android:title="Powiększ"
app:showAsAction="ifRoom" />
<!-- zawsze pod ⋮ -->
<item
android:id="@+id/akcja_o_aplikacji"
android:title="O aplikacji"
app:showAsAction="never" />
</menu>
| showAsAction | Gdzie pojawi się pozycja |
|---|---|
ifRoom | Ikona na pasku, gdy jest miejsce; gdy brak — pod ⋮. Najczęstszy wybór. |
never | Zawsze w rozwijanym menu pod ⋮ (jako tekst). |
always | Zawsze na pasku — używaj oszczędnie, bo na wąskim ekranie zabraknie miejsca na tytuł. |
android:title jest potrzebny także przy ikonie: po przytrzymaniu ikony pojawia się jako podpowiedź i czyta go czytnik ekranu. Zwróć uwagę na app:showAsAction, a nie android:showAsAction — w aplikacjach z AppCompatActivity obowiązuje wersja app:.
Pokazanie menu
Android sam pyta aktywność, czy ma menu. Odpowiadasz, nadpisując metodę onCreateOptionsMenu — wczytujesz w niej plik XML (podobnie jak LayoutInflater w lekcji 28):
override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.menu_main, menu) // XML → pozycje menu
return true // true = pokaż menu
}
Nie wywołujesz tej metody sam — Android robi to po onCreate. Wystarczy ją napisać w klasie aktywności (wpisz onCreateOp i wybierz podpowiedź — Android Studio wstawi szkielet).
Reakcja na wybór
Wszystkie kliknięcia w menu trafiają do jednej metody. Rozpoznajesz pozycję po itemId — to android:id z pliku menu:
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.akcja_powieksz -> zmienRozmiar(2f)
R.id.akcja_o_aplikacji -> pokazOAplikacji()
else -> return super.onOptionsItemSelected(item) // nie nasza pozycja
}
return true // obsłużone
}
lub tekst pod ⋮
return true
Pozycja z zaznaczeniem
Pozycja z android:checkable="true" ma w menu pole wyboru — dobre dla ustawień typu włącz / wyłącz. Uwaga: Android nie przełącza zaznaczenia sam. Robisz to w kodzie:
R.id.akcja_tryb_nocny -> {
trybNocny = !trybNocny
item.isChecked = trybNocny // pole wyboru w menu
pokazWyglad()
}
Notatnik z menu
Duże pole na notatkę i pasek z menu. Na pasku dwie ikony: powiększ i pomniejsz czcionkę (12–32 sp, krok 2). Podtytuł paska pokazuje bieżący rozmiar. Pod ⋮: Tryb nocny (z zaznaczeniem), Udostępnij (lekcja 34), Wyczyść (z pytaniem, lekcja 22) i O aplikacji.
– oddać projekt z Androida
– powtórzyć intencje
– kupić zeszyt w kratkę
– oddać projekt z Androida
– powtórzyć intencje
– kupić zeszyt w kratkę
– oddać projekt z Androida
– powtórzyć intencje
– kupić zeszyt w kratkę
Przygotowanie projektu
- Nowy projekt Empty Views Activity, nazwa
Notatnik, Kotlin, API 24. - Ikony: prawy klik na
res→ New → Vector Asset → Clip art:ic_powieksz(text increase) iic_pomniejsz(text decrease). - Plik menu: prawy klik na
res→ New → Android Resource File, nazwamenu_main, typ Menu. - Podmień
activity_main.xml,menu_main.xmliMainActivity.kt.
├── java/…/MainActivity.kt
└── res/
├── drawable/ic_powieksz.xml, ic_pomniejsz.xml
├── layout/activity_main.xmlpasek + pole notatki
└── menu/menu_main.xmlnowy folder i plik
<?xml version="1.0" encoding="utf-8"?>
<!-- bez marginesu: pasek ma sięgać krawędzi ekranu -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:fitsSystemWindows="true">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimaryContainer"
app:title="Notatnik" />
<EditText
android:id="@+id/etNotatka"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:background="@null"
android:gravity="top"
android:hint="Pisz tutaj…"
android:inputType="textMultiLine|textCapSentences"
android:padding="16dp" />
</LinearLayout>
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/akcja_powieksz"
android:icon="@drawable/ic_powieksz"
android:title="Powiększ czcionkę"
app:showAsAction="ifRoom" />
<item
android:id="@+id/akcja_pomniejsz"
android:icon="@drawable/ic_pomniejsz"
android:title="Pomniejsz czcionkę"
app:showAsAction="ifRoom" />
<item
android:id="@+id/akcja_tryb_nocny"
android:checkable="true"
android:title="Tryb nocny"
app:showAsAction="never" />
<item
android:id="@+id/akcja_udostepnij"
android:title="Udostępnij"
app:showAsAction="never" />
<item
android:id="@+id/akcja_wyczysc"
android:title="Wyczyść"
app:showAsAction="never" />
<item
android:id="@+id/akcja_o_aplikacji"
android:title="O aplikacji"
app:showAsAction="never" />
</menu>
package pl.schoolit.notatnik
import android.content.Intent
import android.graphics.Color
import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
import android.widget.EditText
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import com.google.android.material.appbar.MaterialToolbar
class MainActivity : AppCompatActivity() {
// ---------- kontrolki ----------
private lateinit var toolbar: MaterialToolbar
private lateinit var etNotatka: EditText
// ---------- stan ----------
private val MIN_ROZMIAR = 12f
private val MAX_ROZMIAR = 32f
private var rozmiar = 18f
private var trybNocny = false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
initUI()
setSupportActionBar(toolbar) // pasek z układu staje się paskiem akcji
pokazWyglad()
}
private fun initUI() {
toolbar = findViewById(R.id.toolbar)
etNotatka = findViewById(R.id.etNotatka)
}
// ---------- menu ----------
override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.menu_main, menu)
return true
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.akcja_powieksz -> zmienRozmiar(2f)
R.id.akcja_pomniejsz -> zmienRozmiar(-2f)
R.id.akcja_tryb_nocny -> {
trybNocny = !trybNocny
item.isChecked = trybNocny // Android sam tego nie zrobi
pokazWyglad()
}
R.id.akcja_udostepnij -> udostepnij()
R.id.akcja_wyczysc -> zapytajOWyczyszczenie()
R.id.akcja_o_aplikacji -> pokazOAplikacji()
else -> return super.onOptionsItemSelected(item)
}
return true
}
// ---------- akcje ----------
private fun zmienRozmiar(zmiana: Float) {
val nowy = rozmiar + zmiana
if (nowy < MIN_ROZMIAR || nowy > MAX_ROZMIAR) {
Toast.makeText(this, "Rozmiar od 12 do 32 sp", Toast.LENGTH_SHORT).show()
return
}
rozmiar = nowy
pokazWyglad()
}
private fun udostepnij() {
val tekst = etNotatka.text.toString().trim()
if (tekst.isEmpty()) {
Toast.makeText(this, "Notatka jest pusta", Toast.LENGTH_SHORT).show()
return
}
val intencja = Intent(Intent.ACTION_SEND) // lekcja 34
intencja.type = "text/plain"
intencja.putExtra(Intent.EXTRA_TEXT, tekst)
startActivity(Intent.createChooser(intencja, "Udostępnij notatkę"))
}
private fun zapytajOWyczyszczenie() {
if (etNotatka.text.isEmpty()) return
AlertDialog.Builder(this)
.setTitle("Wyczyścić notatkę?")
.setMessage("Tekst zostanie usunięty.")
.setPositiveButton("Wyczyść") { _, _ -> etNotatka.text.clear() }
.setNegativeButton("Anuluj", null)
.show()
}
private fun pokazOAplikacji() {
AlertDialog.Builder(this)
.setTitle("Notatnik")
.setMessage("Wersja 1.0\nPrzykład z lekcji 35 kursu Android + Kotlin.")
.setPositiveButton("OK", null)
.show()
}
// ---------- stan → ekran ----------
private fun pokazWyglad() {
etNotatka.textSize = rozmiar // w sp
supportActionBar?.subtitle = "Czcionka: ${rozmiar.toInt()} sp"
if (trybNocny) {
etNotatka.setBackgroundColor(Color.parseColor("#1C1B1F"))
etNotatka.setTextColor(Color.parseColor("#E6E1E5"))
} else {
etNotatka.setBackgroundColor(Color.WHITE)
etNotatka.setTextColor(Color.BLACK)
}
}
}
Co dzieje się po kolei
- Układ zaczyna się od paska
MaterialToolbar.setSupportActionBar(toolbar)mówi aktywności, żeby tam pokazywała tytuł i menu. - Po
onCreateAndroid wywołujeonCreateOptionsMenu: plikmenu_main.xmlzamienia się w dwie ikony i cztery pozycje pod ⋮. - Każde dotknięcie pozycji trafia do
onOptionsItemSelected;whenpoitemIdwywołuje odpowiednią metodę. zmienRozmiarpilnuje zakresu 12–32 sp. Poza nim pokazuje Toast i kończy metodę przezreturn.- Tryb nocny przełącza pole w kodzie i ręcznie ustawia
item.isChecked. pokazWyglad()ustawia rozmiar, podtytuł i kolory — wszystko w jednym miejscu.
Sprawdź trzy rzeczy
- Przytrzymaj ikonę A+ na pasku. Pojawi się dymek z tekstem „Powiększ czcionkę” — to
android:title. - Obróć ekran (emulator: ikona obrotu na pasku bocznym). W poziomie jest więcej miejsca — sprawdź, czy któraś pozycja nie przeskoczyła na pasek. Zauważ też, że rozmiar czcionki i tryb nocny wróciły do początkowych. Dlaczego — w lekcji 36.
- Usuń z
onOptionsItemSelectedlinięitem.isChecked = trybNocny. Tryb nocny działa, ale pole wyboru w menu się nie zmienia.
Zestawienie elementów
| Element | Typ | Co robi |
|---|---|---|
MaterialToolbar | kontrolka | Górny pasek aplikacji. |
setSupportActionBar(toolbar) | metoda | Czyni pasek z układu paskiem akcji aktywności. |
supportActionBar?.title / subtitle | właściwości | Tytuł i podtytuł paska z kodu. |
res/menu/*.xml, <item> | zasób | Opis pozycji menu. |
app:showAsAction="ifRoom | never | always" | atrybut | Ikona na pasku albo pozycja pod ⋮. |
android:checkable="true" | atrybut | Pozycja z polem wyboru. |
override fun onCreateOptionsMenu(menu: Menu): Boolean | metoda | Tworzy menu; menuInflater.inflate(…). |
override fun onOptionsItemSelected(item: MenuItem): Boolean | metoda | Reakcja na wybór; item.itemId. |
item.isChecked | Boolean | Zaznaczenie pozycji. |
onPrepareOptionsMenu(menu) + invalidateOptionsMenu() | metody | Zmiana menu w trakcie działania (np. wyłączenie pozycji) — zadanie 5. |
editText.textSize = 18f | właściwość | Rozmiar tekstu w sp. |
Zanim utkniesz
| Zapis / objaw | Problem |
|---|---|
| menu w ogóle się nie pokazuje | Brak setSupportActionBar(toolbar) albo onCreateOptionsMenu zwraca false. |
wszystkie pozycje są pod ⋮, mimo showAsAction | Użyto android:showAsAction. W AppCompat musi być app:showAsAction (i xmlns:app w <menu>). |
Unresolved reference: menu przy R.menu.menu_main | Plik nie leży w folderze res/menu albo ma inną nazwę. |
| pole wyboru „Tryb nocny” się nie zmienia | Brak item.isChecked = … — Android nie przełącza zaznaczenia sam. |
| ikona na pasku ma kolor tła — niewidoczna | Kolor ikony ustawiony na stałe w pliku wektora. Zostaw android:tint="?attr/colorControlNormal" z Vector Asset. |
| pasek nachodzi na pasek stanu | Brak android:fitsSystemWindows="true" w głównym układzie. |
onOptionsItemSelected bez else -> return super… | Działa w tym przykładzie, ale później zepsuje strzałkę „wstecz” na pasku (android.R.id.home) — zostawiaj super. |
Zadania
Każde zadanie wykonaj w nowym projekcie Empty Views Activity z paskiem MaterialToolbar. Wskazówkę otwórz dopiero wtedy, gdy utkniesz.
Duża liczba na środku ekranu. Na pasku ikony + i − (add, remove), pod ⋮ pozycja Zeruj. Tytuł paska: „Licznik”, podtytuł: „Kliknięć: 12” (wszystkie kliknięcia, także −).
pokaż wskazówkę
- Dwa pola:
wartosciklikniecia; jedna metodapokazStan()ustawia napis isupportActionBar?.subtitle.
Pod ⋮ cztery pozycje kolorów: Biały, Błękitny, Miętowy, Różowy. Wybór zmienia tło ekranu i podtytuł paska na nazwę koloru.
pokaż wskazówkę
- Nazwę koloru masz w
item.title. Tło:findViewById<View>(R.id.main).setBackgroundColor(…).
Lista uczniów (ListView). Pod ⋮ grupa „Sortuj”: Alfabetycznie, Od końca, Według długości — zaznaczona może być tylko jedna (kółko, nie kwadrat).
pokaż wskazówkę
- W XML:
<group android:checkableBehavior="single">wokół trzech<item>. W kodzie:item.isChecked = true— grupa sama odznaczy pozostałe.
Rozbuduj przykład: O aplikacji otwiera osobną aktywność (lekcja 30) z własnym paskiem i strzałką ← po lewej. Strzałka zamyka ekran.
pokaż wskazówkę
- W drugiej aktywności:
setSupportActionBar(toolbar),supportActionBar?.setDisplayHomeAsUpEnabled(true), a wonOptionsItemSelected:android.R.id.home -> finish().
Rozbuduj przykład: zamiast Toastu na granicy zakresu pozycja A+ jest wyłączona przy 32 sp, a A− przy 12 sp. Wyczyść i Udostępnij są wyłączone, gdy notatka jest pusta.
pokaż wskazówkę
- Nadpisz
onPrepareOptionsMenu(menu):menu.findItem(R.id.akcja_powieksz).isEnabled = rozmiar < MAX_ROZMIAR. Po każdej zmianie wywołajinvalidateOptionsMenu()— Android przygotuje menu od nowa. Zmianę tekstu wykryjesz przezetNotatka.addTextChangedListener { invalidateOptionsMenu() }.
Przebuduj przykład z lekcji 26: przyciski Sortuj i Usuń kupione przenieś do menu (ikona sort na pasku, „Usuń kupione” pod ⋮). Dodaj pod ⋮ Udostępnij listę (niekupione produkty, każdy w nowej linii) i Wyczyść wszystko z pytaniem. Licznik produktów pokaż w podtytule paska.
pokaż wskazówkę
- Tekst do udostępnienia:
zakupy.filter { !it.startsWith(KUPIONE) }.joinToString("\n"). WpokazListe()ustawsupportActionBar?.subtitle.
Co trzeba zapamiętać
- Pasek:
MaterialToolbarw układzie +setSupportActionBar(toolbar). - Menu: plik w
res/menu, pozycje<item>zid,title, opcjonalnieiconiapp:showAsAction. onCreateOptionsMenuwczytuje plik,onOptionsItemSelectedreaguje na wybór poitemId.- Pozycje z zaznaczeniem przełączasz sam:
item.isChecked = …. - Rzadziej używane czynności — pod ⋮; najważniejsze — ikoną na pasku.
Dokumentacja: Menu, Konfiguracja paska aplikacji.