Układy w Androidzie
LinearLayout, ConstraintLayout, RelativeLayout i GridLayout – właściwości XML, konfiguracja i kiedy używać którego układu.
Czym jest układ?
Układ (Layout) to niewidoczny kontener, który organizuje rozmieszczenie elementów interfejsu na ekranie. Sam nic nie wyświetla – mówi Androidowi gdzie i jak mają być ułożone jego dzieci (TextView, Button, ImageView itp.).
Układy można zagnieżdżać – LinearLayout może zawierać wewnątrz siebie ConstraintLayout, a ten z kolei inne widoki. Jednak głębokie zagnieżdżanie spowalnia renderowanie ekranu.
Każdy element w XML to albo układ (ViewGroup – ma dzieci) albo widok (View – nie ma dzieci). LinearLayout, ConstraintLayout, RelativeLayout, GridLayout to układy. TextView, Button, EditText, ImageView to widoki.
Właściwości wspólne dla wszystkich układów i widoków
Te atrybuty działają w każdym układzie i na każdym widoku – warto je znać na pamięć.
| Atrybut XML | Wartości | Opis |
|---|---|---|
| android:layout_width | match_parent / wrap_content / Xdp | Szerokość elementu. match_parent = tyle co rodzic. wrap_content = tyle ile zajmuje treść. Xdp = stała liczba dp (np. 200dp). |
| android:layout_height | match_parent / wrap_content / Xdp | Wysokość elementu. Takie same wartości jak layout_width. |
| android:id | @+id/nazwaId | Unikalny identyfikator widoku. Wymagany gdy chcemy się do niego odwołać z kodu Kotlin przez findViewById(). |
| android:padding | Xdp | Wewnętrzny odstęp od krawędzi – między ramką a treścią. Można ustawić osobno: paddingTop, paddingBottom, paddingStart, paddingEnd. |
| android:layout_margin | Xdp | Zewnętrzny odstęp od sąsiednich elementów. Osobno: marginTop, marginBottom, marginStart, marginEnd. |
| android:background | #RRGGBB / @color / @drawable | Kolor tła lub rysunek tła elementu. |
| android:visibility | visible / invisible / gone | visible = widoczny. invisible = niewidoczny ale zajmuje miejsce. gone = niewidoczny i nie zajmuje miejsca. |
| android:alpha | 0.0 – 1.0 | Przezroczystość. 0 = całkowicie przezroczysty. 1 = nieprzezroczysty. |
dp (density-independent pixels) – jednostka do rozmiarów elementów. 1dp wygląda tak samo na każdym ekranie niezależnie od rozdzielczości.
sp (scale-independent pixels) – używana wyłącznie do rozmiarów tekstu (textSize). Respektuje ustawienia rozmiaru czcionki użytkownika.
LinearLayout
LinearLayout układa swoje dzieci w jednej linii – albo pionowo jeden pod drugim, albo poziomo obok siebie. To najprostszy układ – idealny do nauki i prostych ekranów.
Właściwości LinearLayout
| Atrybut XML | Wartości | Opis |
|---|---|---|
| android:orientation | vertical / horizontal | Kierunek układania dzieci. vertical = jeden pod drugim. horizontal = obok siebie. Domyślnie: horizontal. |
| android:gravity | center / top / bottom / start / end / center_horizontal / center_vertical | Wyrównanie wszystkich dzieci wewnątrz kontenera. np. gravity=”center” wyśrodkuje wszystkie elementy. |
| android:weightSum | liczba całkowita | Suma wag dla layout_weight. Jeśli dzieci mają wagi 1, 2, 1 i weightSum=4 – proporcje są precyzyjne. Opcjonalne. |
| android:divider | @drawable/linia | Graficzny separator między dziećmi. Wymaga też showDividers. |
| android:showDividers | beginning / middle / end / none | Gdzie pokazywać separatory: przed, między lub po elementach. |
| android:baselineAligned | true / false | Czy wyrównać tekst dzieci po linii bazowej. Domyślnie true. Dla horizontal z różnymi rozmiarami tekstu. |
layout_weight – proporcjonalne rozmiary
android:layout_weight to jedna z najważniejszych właściwości w LinearLayout. Pozwala rozdzielić dostępne miejsce proporcjonalnie między dzieci. Ustawiamy ją na dziecku, nie na kontenerze.
| Atrybut XML | Wartości | Opis |
|---|---|---|
| android:layout_weight | liczba (np. 1, 2, 0.5) | Waga elementu – ile proporcjonalnego miejsca zajmie. Działą tylko w LinearLayout. Aby działało poprawnie ustaw layout_width=”0dp” (horizontal) lub layout_height=”0dp” (vertical). |
| android:layout_gravity | center / top / bottom / start / end / fill | Wyrównanie tego konkretnego dziecka w osi prostopadłej do orientation. Różni się od gravity (które dotyczy wszystkich dzieci). |
Gdy używasz layout_weight na elementach poziomego LinearLayout, ustaw android:layout_width="0dp". Gdy pionowego – android:layout_height="0dp". Wartość 0dp oznacza „Android sam obliczy rozmiar na podstawie wagi”.
gravity vs layout_gravity
android:gravity
Ustawiamy na kontenerze (LinearLayout). Określa jak rozmieszczone są wszystkie dzieci wewnątrz kontenera.
android:layout_gravity
Ustawiamy na dziecku. Określa jak to konkretne dziecko jest wyrównane w osi prostopadłej do orientation rodzica.
Przykład kompletny
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" <!-- pionowo, jeden pod drugim --> android:padding="16dp" android:gravity="center_horizontal" <!-- wszystkie dzieci wyśrodkowane poziomo --> android:background="#FAFAFA"> <!-- Nagłówek --> <TextView android:id="@+id/tvTytul" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Logowanie" android:textSize="26sp" android:textStyle="bold" android:layout_marginBottom="24dp" /> <!-- Pole tekstowe – pełna szerokość --> <EditText android:id="@+id/etLogin" android:layout_width="match_parent" <!-- pełna szerokość rodzica --> android:layout_height="wrap_content" android:hint="Login" android:inputType="text" android:layout_marginBottom="12dp" /> <EditText android:id="@+id/etHaslo" android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="Hasło" android:inputType="textPassword" android:layout_marginBottom="20dp" /> <!-- Rząd przycisków z wagami – każdy zajmuje połowę szerokości --> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:gravity="center"> <Button android:id="@+id/btnAnuluj" android:layout_width="0dp" <!-- 0dp + weight = proporcjonalna szerokość --> android:layout_weight="1" android:layout_height="wrap_content" android:text="Anuluj" android:layout_marginEnd="8dp" /> <Button android:id="@+id/btnZaloguj" android:layout_width="0dp" android:layout_weight="1" android:layout_height="wrap_content" android:text="Zaloguj" /> </LinearLayout> </LinearLayout>
ConstraintLayout
ConstraintLayout pozycjonuje każdy widok przez wiązania (constraints) – połączenia krawędzi widoku z krawędzią innego widoku lub rodzica. To domyślny układ w nowych projektach Android Studio.
Każdy widok w ConstraintLayout musi mieć co najmniej dwa wiązania – jedno poziome i jedno pionowe. Bez nich Android nie wie gdzie umieścić element i umieszcza go w lewym górnym rogu.
Właściwości ConstraintLayout zaczynają się od app: (nie android:). Dlatego w korzeniu layoutu musi być deklaracja: xmlns:app="http://schemas.android.com/apk/res-auto". Bez niej atrybuty app: nie będą działać.
Właściwości ConstraintLayout
Każde wiązanie mówi: „moja krawędź X jest przyciągnięta do krawędzi Y obiektu Z”.
| Atrybut XML (app:) | Wartości | Opis |
|---|---|---|
| app:layout_constraintTop_toTopOf | parent / @id/innyWidok | Górna krawędź tego widoku przyciągnięta do górnej krawędzi rodzica lub innego widoku. |
| app:layout_constraintBottom_toBottomOf | parent / @id/innyWidok | Dolna krawędź przyciągnięta do dolnej krawędzi celu. |
| app:layout_constraintStart_toStartOf | parent / @id/innyWidok | Lewa krawędź przyciągnięta do lewej krawędzi celu. |
| app:layout_constraintEnd_toEndOf | parent / @id/innyWidok | Prawa krawędź przyciągnięta do prawej krawędzi celu. |
| app:layout_constraintTop_toBottomOf | @id/innyWidok | Górna krawędź przyciągnięta do dolnej krawędzi celu – element jest POD wskazanym widokiem. |
| app:layout_constraintBottom_toTopOf | @id/innyWidok | Dolna krawędź przyciągnięta do górnej krawędzi celu – element jest NAD wskazanym widokiem. |
| app:layout_constraintStart_toEndOf | @id/innyWidok | Lewa krawędź przyciągnięta do prawej krawędzi celu – element jest PO PRAWEJ wskazanego. |
| app:layout_constraintEnd_toStartOf | @id/innyWidok | Prawa krawędź przyciągnięta do lewej krawędzi celu – element jest PO LEWEJ wskazanego. |
Bias – przesunięcie między wiązaniami
Gdy element ma wiązanie z obu stron jednocześnie (np. start i end do parent), Android domyślnie wyśrodkowuje go (bias = 0.5). Możemy to zmienić.
| Atrybut XML (app:) | Wartości | Opis |
|---|---|---|
| app:layout_constraintHorizontal_bias | 0.0 – 1.0 | Przesunięcie poziome. 0.0 = przy lewej krawędzi. 0.5 = wyśrodkowany. 1.0 = przy prawej krawędzi. Wymaga wiązań z obu stron (start i end). |
| app:layout_constraintVertical_bias | 0.0 – 1.0 | Przesunięcie pionowe. 0.0 = na górze. 0.5 = wyśrodkowany. 1.0 = na dole. Wymaga wiązań top i bottom. |
Chain – łańcuch widoków
Gdy kilka widoków jest połączonych wiązaniami w obie strony między sobą, tworzą łańcuch (chain). Pierwszy element łańcucha jest jego „głową” i to na nim ustawiamy styl.
| Atrybut XML (app:) | Wartości | Opis |
|---|---|---|
| app:layout_constraintHorizontal_chainStyle | spread / spread_inside / packed | Styl łańcucha poziomego. spread = elementy rozmieszczone równomiernie z marginesami. spread_inside = bez marginesów na krańcach. packed = elementy zebrane razem w centrum. |
| app:layout_constraintVertical_chainStyle | spread / spread_inside / packed | Styl łańcucha pionowego – identyczne wartości co poziomy. |
Przykład kompletny
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" <!-- wymagane! --> android:layout_width="match_parent" android:layout_height="match_parent"> <!-- Tytuł – wyśrodkowany poziomo, 32dp od góry --> <TextView android:id="@+id/tvTytul" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Profil użytkownika" android:textSize="22sp" android:textStyle="bold" app:layout_constraintTop_toTopOf="parent" <!-- od góry rodzica --> app:layout_constraintStart_toStartOf="parent" <!-- od lewej rodzica --> app:layout_constraintEnd_toEndOf="parent" <!-- i od prawej → wyśrodkowany --> android:layout_marginTop="32dp" /> <!-- Pole tekstowe – POD tytułem, pełna szerokość z marginesami --> <EditText android:id="@+id/etImie" android:layout_width="0dp" android:layout_height="wrap_content" android:hint="Imię i nazwisko" app:layout_constraintTop_toBottomOf="@id/tvTytul" <!-- POD tvTytul --> app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" android:layout_marginTop="20dp" android:layout_marginStart="24dp" android:layout_marginEnd="24dp" /> <!-- Przycisk – wyśrodkowany, lekko powyżej środka ekranu (bias=0.7) --> <Button android:id="@+id/btnZapisz" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Zapisz" app:layout_constraintTop_toBottomOf="@id/etImie" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" android:layout_marginTop="16dp" /> </androidx.constraintlayout.widget.ConstraintLayout>
RelativeLayout
RelativeLayout pozycjonuje elementy względem siebie nawzajem lub względem krawędzi rodzica. Jest prostszy od ConstraintLayout, ale mniej elastyczny. Dobry do layoutów gdzie elementy zależą od siebie pozycyjnie.
Właściwości RelativeLayout
Właściwości dzielą się na dwie grupy: względem rodzica (wartość true/false) i względem innego widoku (wartość @id/nazwaWidoku).
| Atrybut XML (android:) | Wartości | Opis |
|---|---|---|
| Względem rodzica (true/false) | ||
| android:layout_alignParentTop | true | Przyciąga element do górnej krawędzi rodzica. |
| android:layout_alignParentBottom | true | Przyciąga element do dolnej krawędzi rodzica. |
| android:layout_alignParentStart | true | Przyciąga element do lewej krawędzi rodzica. |
| android:layout_alignParentEnd | true | Przyciąga element do prawej krawędzi rodzica. |
| android:layout_centerInParent | true | Wyśrodkowuje element poziomo i pionowo wewnątrz rodzica. |
| android:layout_centerHorizontal | true | Wyśrodkowuje element tylko poziomo. |
| android:layout_centerVertical | true | Wyśrodkowuje element tylko pionowo. |
| Względem innego widoku (@id/…) | ||
| android:layout_below | @id/innyWidok | Umieszcza element poniżej wskazanego widoku. |
| android:layout_above | @id/innyWidok | Umieszcza element powyżej wskazanego widoku. |
| android:layout_toEndOf | @id/innyWidok | Umieszcza element po prawej stronie wskazanego widoku. |
| android:layout_toStartOf | @id/innyWidok | Umieszcza element po lewej stronie wskazanego widoku. |
| android:layout_alignTop | @id/innyWidok | Wyrównuje górną krawędź do górnej krawędzi wskazanego widoku. |
| android:layout_alignBottom | @id/innyWidok | Wyrównuje dolną krawędź do dolnej krawędzi wskazanego widoku. |
| android:layout_alignBaseline | @id/innyWidok | Wyrównuje linię bazową tekstu do linii bazowej wskazanego widoku. |
Przykład kompletny
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:padding="16dp"> <!-- Nagłówek – górna krawędź rodzica --> <TextView android:id="@+id/tvNaglowek" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Lista zadań" android:textSize="20sp" android:textStyle="bold" android:layout_alignParentTop="true" android:layout_alignParentStart="true" /> <!-- Przycisk dodaj – wyrównany do góry i prawej krawędzi rodzica --> <Button android:id="@+id/btnDodaj" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="+ Dodaj" android:layout_alignParentTop="true" android:layout_alignParentEnd="true" /> <!-- Pole tekstowe – poniżej nagłówka, po lewej stronie przycisku --> <EditText android:id="@+id/etZadanie" android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="Wpisz nowe zadanie..." android:layout_below="@id/tvNaglowek" <!-- poniżej nagłówka --> android:layout_toStartOf="@id/btnDodaj" <!-- po lewej stronie przycisku --> android:layout_marginTop="8dp" android:layout_marginEnd="8dp" /> <!-- Przycisk anuluj – dolna prawa krawędź ekranu --> <Button android:id="@+id/btnAnuluj" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Anuluj" android:layout_alignParentBottom="true" android:layout_alignParentEnd="true" android:layout_marginBottom="16dp" /> </RelativeLayout>
GridLayout
GridLayout układa elementy w siatce wierszy i kolumn – jak tabela. Idealny do kalkulatorów, galerii, klawiatur numerycznych i wszelkich regularnych siatek elementów.
Właściwości GridLayout
| Atrybut XML (android:) | Wartości | Opis |
|---|---|---|
| Właściwości kontenera GridLayout | ||
| android:columnCount | liczba całkowita | Liczba kolumn siatki. Np. columnCount=”3″ = trzy kolumny o równej szerokości. |
| android:rowCount | liczba całkowita | Liczba wierszy siatki. Można pominąć – Android obliczy automatycznie. |
| android:orientation | horizontal / vertical | Kierunek wypełniania siatki. horizontal = lewo→prawo, potem następny wiersz. vertical = góra→dół, potem następna kolumna. |
| Właściwości dzieci GridLayout | ||
| android:layout_column | liczba (od 0) | Numer kolumny gdzie umieścić element (indeksowanie od 0). Bez tego Android umieszcza automatycznie. |
| android:layout_row | liczba (od 0) | Numer wiersza gdzie umieścić element (indeksowanie od 0). |
| android:layout_gravity | fill / fill_horizontal / fill_vertical / center / start / end | Jak element wypełnia swoją komórkę. fill_horizontal rozciąga element na całą szerokość kolumny – ważne dla równych szerokości. |
Łączenie komórek (Span)
Element może zajmować więcej niż jedną kolumnę lub wiersz – jak „merge cells” w Excelu.
| Atrybut XML (android:) | Wartości | Opis |
|---|---|---|
| android:layout_columnSpan | liczba całkowita | Ile kolumn zajmuje element. Np. columnSpan=”2″ = element rozciąga się na 2 kolumny. Zwykle łączymy z layout_gravity=”fill_horizontal”. |
| android:layout_rowSpan | liczba całkowita | Ile wierszy zajmuje element. Np. rowSpan=”2″ = element zajmuje 2 wiersze w pionie. Łączymy z layout_gravity=”fill_vertical”. |
Przykład kompletny – kalkulator numeryczny
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:padding="16dp"> <!-- Ekran kalkulatora --> <TextView android:id="@+id/tvEkran" android:layout_width="match_parent" android:layout_height="80dp" android:text="0" android:textSize="36sp" android:gravity="end|center_vertical" android:padding="8dp" android:background="#212121" android:textColor="#FFFFFF" android:layout_marginBottom="12dp" /> <!-- Klawiatura numeryczna w siatce 3 kolumny --> <GridLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:columnCount="3" <!-- 3 kolumny --> android:rowCount="4" android:useDefaultMargins="true"> <!-- Wiersz 1: 7, 8, 9 --> <Button android:text="7" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_columnWeight="1" /> <Button android:text="8" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_columnWeight="1" /> <Button android:text="9" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_columnWeight="1" /> <!-- Wiersz 2: 4, 5, 6 --> <Button android:text="4" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_columnWeight="1" /> <Button android:text="5" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_columnWeight="1" /> <Button android:text="6" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_columnWeight="1" /> <!-- Wiersz 3: 1, 2, 3 --> <Button android:text="1" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_columnWeight="1" /> <Button android:text="2" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_columnWeight="1" /> <Button android:text="3" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_columnWeight="1" /> <!-- Wiersz 4: 0 (zajmuje 2 kolumny) i = --> <Button android:text="0" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_columnWeight="1" android:layout_columnSpan="2" /> <!-- zajmuje 2 kolumny --> <Button android:text="=" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_columnWeight="1" /> </GridLayout> </LinearLayout>
Kiedy używać którego układu?
| Układ | Kiedy używać | Unikaj gdy |
|---|---|---|
| LinearLayout | Prosta lista elementów pionowo lub poziomo. Formularze. Paski przycisków. Gdy potrzebujesz wag (weight). | Złożone pozycjonowanie. Nakładanie elementów. Duże zagnieżdżenia. |
| ConstraintLayout | Złożone ekrany z wieloma widokami. Gdy chcesz uniknąć zagnieżdżania. Responsywne layouty na różne ekrany. Zalecany w nowych projektach. | Bardzo proste ekrany z 2–3 elementami – LinearLayout będzie czytelniejszy. |
| RelativeLayout | Elementy pozycjonowane względem siebie (np. przycisk zawsze obok pola tekstowego). Layouty z elementami przylepionymi do krawędzi. | Nowe projekty – użyj ConstraintLayout. RelativeLayout jest starszy i mniej elastyczny. |
| GridLayout | Kalkulatory. Klawiatury numeryczne. Galerie. Każdy układ regularnej siatki. Gdy potrzebujesz elementów zajmujących kilka kolumn/wierszy. | Listy danych o zmiennej długości – użyj RecyclerView. Nieregularne układy. |
Nie zagnieżdżaj layoutów zbyt głęboko. Każdy poziom zagnieżdżenia spowalnia renderowanie. Zamiast 3 zagnieżdżonych LinearLayout – użyj jednego ConstraintLayout. Android musi „zmierzyć” każdy element dwukrotnie dla każdego poziomu zagnieżdżenia.
W praktyce często łączymy układy. Np. ConstraintLayout jako root z LinearLayout wewnątrz dla rzędu przycisków, a GridLayout dla klawiatury. Kluczowe pytanie: czy ten konkretny fragment najprościej opisać jako liniowy, siatkowy czy relatywny?
Zadanie – ćwiczenia z układami
Zadanie 1 – LinearLayout (formularz rejestracji)
- Stwórz nowy projekt z
Empty Views Activity, język Kotlin - Zmień layout na
LinearLayoutvertical z paddingiem 20dp - Dodaj TextView „Rejestracja” (textSize 24sp, bold, marginBottom 20dp)
- Dodaj EditText na imię, nazwisko i e-mail (każdy match_parent, hint z opisem)
- Na dole dodaj LinearLayout horizontal z dwoma przyciskami „Wyczyść” i „Zarejestruj” – każdy z wagą 1 i layout_width=”0dp”
Zadanie 2 – GridLayout (kalkulator)
- Stwórz nową aktywność lub projekt
- Na górze TextView jako ekran kalkulatora (textSize 36sp, gravity=end)
- Pod nim GridLayout z 4 kolumnami: cyfry 0–9, operatory +−×÷, i przycisk = zajmujący 2 kolumny (columnSpan=”2″)
- Użyj layout_columnWeight=”1″ dla równych szerokości przycisków
⭐ Bonus 1: w zadaniu 1 zastosuj ConstraintLayout zamiast LinearLayout – wyśrodkuj przycisk „Zarejestruj” poziomo (start+end do parent)
⭐⭐ Bonus 2: w zadaniu 2 obsłuż kliknięcia przycisków w Kotlinie – wyświetl klikniętą cyfrę na ekranie (TextView)
⭐⭐⭐ Bonus 3: użyj RelativeLayout – umieść przycisk „Wyczyść” zawsze w prawym dolnym rogu ekranu (alignParentBottom + alignParentEnd), a etykietę „Kalkulator” w lewym górnym rogu