Adaptive Layout: kluczowe pojęcia, klasy rozmiarów i kwalifikatory

Autor: IT Sectr Opublikowano: 2026-02-26 Czas czytania: 10 min

Adaptive Layout — podejście do projektowania interfejsów, w którym aplikacja używa różnych układów dla różnych typów urządzeń i orientacji ekranu. W przeciwieństwie do responsive design, adaptive layout przełącza się między wcześniej przygotowanymi układami w punktach breakpoint. Artykuł omawia Size Classes i UITraitCollection w iOS, kwalifikatory sw600dp/layout-w600dp w Android oraz podaje praktyczne przykłady.

Najważniejsze

  • Adaptive Layout — dyskretne przełączanie układów w zależności od rozmiaru ekranu, orientacji i typu urządzenia
  • Size Classes w iOS klasyfikują przestrzeń jako Compact lub Regular w poziomie i pionie
  • UITraitCollection — kontener cech środowiska iOS (rozmiar, skala, force touch, gama kolorów)
  • sw600dp — kwalifikator minimalnej szerokości Androida dla tabletów (7" i więcej)
  • layout-w600dp — kwalifikator dostępnej szerokości, uruchamiany przy wystarczającej przestrzeni w bieżącej orientacji

Czym jest Adaptive Layout?

Adaptive Layout — strategia budowania interfejsu, w której programista tworzy osobne układy dla każdego typu urządzenia i przełącza się między nimi na podstawie cech ekranu. W przeciwieństwie do responsive design, gdzie elementy płynnie się przemieszczają, adaptive layout używa dyskretnych przełączeń: telefon — jeden layout, tablet — drugi, desktop — trzeci.

Apple wprowadziło Size Classes w iOS 8 (2014) wraz z uniwersalnymi aplikacjami (universal binaries, działającymi zarówno na iPhone, jak i iPad). Google przedstawiła kwalifikator smallestWidth (swdp) w Android 3.2 (2011). Oba mechanizmy rozwiązują to samo zadanie: dać programiście narzędzie do tworzenia różnych UI dla różnych ekranów bez powielania kodu.

Nowoczesne zalecenia Apple HIG i Google Material Design są zgodne, że adaptive layout jest obowiązkowy dla aplikacji obsługujących więcej niż jeden rozmiar ekranu. Google Play oznacza brak adaptacji dla tabletów jako wadę. App Store nie ma formalnego wymogu, ale użytkownicy iPada oczekują natywnego doświadczenia, a nie rozciągniętej aplikacji iPhone'a.

iOS Size Classes i UITraitCollection

Size Classes — cecha iOS określająca dostępną przestrzeń jako kompaktową (Compact) lub regularną (Regular) w każdej osi. UITraitCollection — system iOS do przekazywania cech środowiska przez hierarchię view: rozmiar, skalę ekranu, force touch capability, gamę kolorów (light/dark), accessibility settings.

Urządzenie / OrientacjaHorizontalVertical
iPhone portret (wszystkie modele)CompactRegular
iPhone Plus/Pro Max krajobrazRegularCompact
iPhone SE / mini krajobrazCompactCompact
iPad portretRegularRegular
iPad krajobrazRegularRegular
iPad Split View (1/3 ekranu)CompactRegular

UITraitCollection jest tworzony przez system i przekazywany od UIApplication przez UIWindow do każdego UIView. Przy zmianie orientacji lub rozmiaru okna (Split View) system generuje nowy UITraitCollection i wywołuje traitCollectionDidChange. W SwiftUI zmiany są śledzone przez Environment Values i automatycznie przerysowują view.

Przykład adaptacyjnego layoutu w Swift

Adaptacja przez UITraitCollection w UIKit jest realizowana przez subskrypcję zmian trait collection i przebudowę layoutu. Poniższy przykład pokazuje kontroler, który przełącza się między pionowym stackiem dla iPhone'a a split-układem dla iPada.

swift
// Adaptacyjny kontroler z Size Classes
import UIKit

final class AdaptiveViewController: UIViewController {

    private let compactStack = UIStackView()
    private let regularStack = UIStackView()
    private let sidebar = UIView()
    private let content = UIView()

    override func viewDidLoad() {
        super.viewDidLoad()
        setupViews()
        updateLayout(for: traitCollection)
    }

    override func traitCollectionDidChange(
        _ previousTraitCollection: UITraitCollection?
    ) {
        super.traitCollectionDidChange(previousTraitCollection)
        if traitCollection.horizontalSizeClass
            != previousTraitCollection?.horizontalSizeClass {
            updateLayout(for: traitCollection)
        }
    }

    private func updateLayout(for traits: UITraitCollection) {
        if traits.horizontalSizeClass == .regular {
            showRegularLayout() // iPad: sidebar + content
        } else {
            showCompactLayout() // iPhone: pasek kart + push
        }
    }

    private func showRegularLayout() {
        view.subviews.forEach { $0.removeFromSuperview() }
        regularStack.addArrangedSubview(sidebar)
        regularStack.addArrangedSubview(content)
        regularStack.frame = view.bounds
        regularStack.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        view.addSubview(regularStack)
    }

    private func showCompactLayout() {
        view.subviews.forEach { $0.removeFromSuperview() }
        compactStack.addArrangedSubview(content)
        compactStack.frame = view.bounds
        compactStack.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        view.addSubview(compactStack)
    }

    private func setupViews() {
        sidebar.backgroundColor = .systemGray6
        content.backgroundColor = .systemBackground
        compactStack.axis = .vertical
        regularStack.axis = .horizontal
    }
}

Metoda traitCollectionDidChange jest wywoływana przy każdej zmianie traits, ale sprawdzamy tylko horizontalSizeClass — najważniejszy wskaźnik do zmiany układu. W iOS 17+ zaleca się używanie UIViewController.horizontalSizeClass jako computed property do reaktywnego odświeżania bez subskrypcji zmian.

Kwalifikatory Androida: sw600dp i w600dp

Android udostępnia dwa główne kwalifikatory szerokości: swdp (smallestWidth) i wdp (available width). Różnica jest krytyczna: sw — minimalna szerokość ekranu niezależnie od orientacji, w — dostępna szerokość uwzględniająca bieżącą orientację. sw gwarantuje, że layout zostanie załadowany dla urządzenia, które fizycznie ma podaną minimalną szerokość. w reaguje na obrót — w trybie krajobrazu może zadziałać w600dp, nawet jeśli w porcie szerokość wynosi 360 dp.

KwalifikatorWarunekPrzykład urządzenia
layout-sw600dpMinimalna szerokość ≥ 600 dpiPad (768 dp), Pixel C (900 dp)
layout-w600dpBieżąca szerokość ≥ 600 dpiPad w Split View (600 dp), telefon w krajobrazie
layout-landOrientacja krajobrazDowolne urządzenie obrócone poziomo
layout-sw720dpMinimalna szerokość ≥ 720 dpiPad Pro (833 dp), Galaxy Tab S9 (800 dp)

Hierarchia zasobów w Android: res/layout/activity_main.xml (telefon), res/layout-sw600dp/activity_main.xml (tablet 7"), res/layout-w600dp-land/activity_main.xml (telefon w krajobrazie). Android wybiera najbardziej specyficzny kwalifikator pasujący do bieżącej konfiguracji urządzenia. W przypadku braku odpowiedniego pliku używany jest podstawowy zasób z res/layout/.

Przykład adaptacyjnego layoutu w Kotlin

Nowoczesne programowanie na Androida używa Jetpack WindowManager do określania rozmiaru okna zamiast bezpośredniego odczytu zasobów. Jest to szczególnie ważne dla urządzeń składanych, gdzie rozmiar ekranu może zmieniać się dynamicznie (Galaxy Fold rozwija się z 6,2" do 7,6").

kotlin
// Adaptacja przez Jetpack WindowManager
import androidx.window.core.layout.WindowHeightSizeClass
import androidx.window.core.layout.WindowSizeClass
import androidx.window.core.layout.WindowWidthSizeClass

@OptIn(ExperimentalLayoutApi::class)
@Composable
fun AdaptiveScreen() {
    val windowSizeClass = currentWindowAdaptiveInfo().windowSizeClass

    FlowRow(
        verticalAlignment = Arrangement.Top,
        horizontalArrangement = when (windowSizeClass.windowWidthSizeClass) {
            WindowWidthSizeClass.EXPANDED -> Arrangement.SpaceEvenly
            else -> Arrangement.Start
        }
    ) {
        when (windowSizeClass.windowWidthSizeClass) {
            WindowWidthSizeClass.COMPACT -> {
                CompactContent()
            }
            WindowWidthSizeClass.MEDIUM -> {
                MediumContent()
            }
            WindowWidthSizeClass.EXPANDED -> {
                ExpandedContent()
            }
        }
    }
}

@Composable
private fun CompactContent() {
    Column(modifier = Modifier.padding(16.dp)) {
        Text("Telefon", style = MaterialTheme.typography.headlineSmall)
        ListContent()
    }
}

@Composable
private fun MediumContent() {
    Row(modifier = Modifier.padding(24.dp)) {
        NavigationRail { ... }
        Column { ListContent() }
    }
}

@Composable
private fun ExpandedContent() {
    Row(modifier = Modifier.padding(32.dp)) {
        PermanentNavigationDrawer { ... }
        Column {
            ListContent()
        }
        DetailPane()
    }
}

Komponent currentWindowAdaptiveInfo() z biblioteki androidx.window adaptuje interfejs do dowolnego urządzenia: telefonu, tabletu, składanego, ChromeOS. Klasy szerokości: COMPACT (0–600 dp), MEDIUM (600–840 dp), EXPANDED (>840 dp). Klasy wysokości: COMPACT (0–480 dp), MEDIUM (480–900 dp), EXPANDED (>900 dp).

Adaptive Layout w SwiftUI

SwiftUI udostępnia wbudowane narzędzia do adaptacyjnego layoutu bez bezpośredniego używania Size Classes. AnyLayout, ViewThatFits i GeometryReader pozwalają budować interfejsy automatycznie dostosowujące się do dostępnej przestrzeni.

swift
// Adaptacja SwiftUI przez AnyLayout i ViewThatFits
import SwiftUI

struct AdaptiveGrid: View {
    @Environment(\.horizontalSizeClass) private var hSizeClass

    var body: some View {
        let layout = hSizeClass == .regular
            ? AnyLayout(HStackLayout())
            : AnyLayout(VStackLayout())

        layout {
            Label("Ulubione", systemImage: "star")
            Label("Ostatnie", systemImage: "clock")
            Label("Ustawienia", systemImage: "gear")
        }
        .padding()
    }
}

// ViewThatFits — automatyczny wybór layoutu
struct SmartLayout: View {
    var body: some View {
        ViewThatFits {
            HStack { // Priorytet 1: poziomy
                CardView()
                CardView()
            }
            VStack { // Jeśli się nie mieści — pionowy
                CardView()
                CardView()
            }
        }
    }
}

ViewThatFits — potężne narzędzie SwiftUI, automatycznie wybierające pierwszy podrzędny view, który mieści się w dostępnej przestrzeni bez przycinania. Eliminuje to potrzebę jawnego sprawdzania Size Classes dla prostych adaptacyjnych przełączeń. AnyLayout pozwala przełączać typ layoutu (HStack/VStack) bez if-else w ciele view.

Adaptive Layout w Jetpack Compose

Jetpack Compose używa WindowSizeClass (z biblioteki Material 3 adaptive) i BoxWithConstraints do adaptacyjnego układu. W przeciwieństwie do kwalifikatorów XML, Compose określa rozmiar okna w runtime i reaktywnie przebudowuje UI przy zmianie konfiguracji.

kotlin
// Adaptacyjny Jetpack Compose przez BoxWithConstraints
@Composable
fun AdaptiveList(items: List<String>) {
    BoxWithConstraints {
        val width = maxWidth

        if (width >= 600.dp) {
            // Tablet: dwie kolumny (list-detail)
            Row(modifier = Modifier.fillMaxSize()) {
                LazyColumn(modifier = Modifier.weight(1f)) {
                    items(items) { item ->
                        ListItem(text = item)
                    }
                }
                var selected by remember { mutableStateOf(items.first()) }
                DetailPanel(item = selected)
                    .weight(2f)
            }
        } else {
            // Telefon: jedna kolumna z nawigacją
            LazyColumn(modifier = Modifier.fillMaxSize()) {
                items(items) { item ->
                    ListItem(text = item, onClick = { navigateToDetail(item) })
                }
            }
        }
    }
}

@Composable
fun AdaptiveListMaterial3() {
    val windowClass = currentWindowAdaptiveInfo().windowSizeClass

    AdaptiveLayout(
        layout = windowClass.windowWidthSizeClass
    ) {
        when (windowClass.windowWidthSizeClass) {
            WindowWidthSizeClass.COMPACT -> {
                ListDetailScaffold(
                    isDetailOnly = false,
                    list = { ListPane() },
                    detail = { DetailPane() }
                )
            }
            else -> {
                ListDetailScaffold(
                    isDetailOnly = false,
                    list = { ListPane() },
                    detail = { DetailPane() }
                )
            }
        }
    }
}

Material 3 Adaptive Layouts udostępnia gotowe komponenty: ListDetailScaffold, SupportingPaneScaffold, NavigationSuiteScaffold. Komponenty te automatycznie dostosowują się do WindowWidthSizeClass — przełączają się między stacked (COMPACT) a side-by-side (MEDIUM/EXPANDED). Programista musi tylko wybrać wzorzec i przekazać panele treści.

Często zadawane pytania

Jaka jest różnica między sw600dp a w600dp?

sw600dp (smallestWidth) — minimalna szerokość ekranu niezależnie od orientacji. Działa na tabletach 7"+ zawsze. w600dp (available width) — dostępna szerokość uwzględniająca bieżącą orientację. Działa na telefonach w krajobrazie, gdzie szerokość może przekraczać 600 dp.

Czym jest UITraitCollection w iOS?

UITraitCollection — obiekt iOS zawierający cechy środowiska: size classes (horizontal/vertical), display scale, force touch capability, user interface idiom (iPhone/iPad), color gamut, accessibility settings. Jest przekazywany od UIApplication przez hierarchię view i zmienia się przy zmianie orientacji lub rozmiaru okna.

Jak dostosować aplikację do urządzeń składanych?

Używaj Jetpack WindowManager (Android) i UIScreen nativeBounds + traitCollection (iOS). Urządzenia składane zmieniają Size Class przy rozkładaniu. Zalecana jest architektura oparta na kanonicznych layoutach (list-detail, supporting pane) z reaktywną subskrypcją zmian rozmiaru okna bez restartu Activity.

Czy trzeba tworzyć osobne storyboards dla iPada?

Nie. Używaj jednego storyboard z wariacjami Size Classes constraint lub programowego layoutu przez UIKit. SwiftUI w ogóle nie wymaga storyboards. Osobne storyboards dla iPada tworzą powielanie i utrudniają utrzymanie — lepiej dostosować jeden układ przez Size Classes.

Czym są canonical layouts w Material 3?

Canonical layouts — gotowe wzorce adaptacyjnego układu od Google: list-detail (lista + szczegóły), supporting pane (treść + panel działań), feed (karty). Każdy wzorzec obsługuje trzy stany (compact/medium/expanded) i jest zaimplementowany w bibliotece Material 3 Adaptive przez ListDetailScaffold i SupportingPaneScaffold.

Podsumowanie

  • Adaptive Layout — dyskretne przełączanie układów w punktach breakpoint dla różnych urządzeń
  • Size Classes iOS (Compact/Regular) określają layout dla iPhone, iPad i Split View
  • UITraitCollection — mechanizm przekazywania cech środowiska przez hierarchię view
  • sw600dp — kwalifikator minimalnej szerokości Androida dla tabletów 7"+
  • w600dp — kwalifikator dostępnej szerokości, uruchamiany przy obrocie w krajobraz
  • Jetpack WindowManager udostępnia WindowSizeClass (WindowWidthSizeClass: COMPACT/MEDIUM/EXPANDED)
  • Material 3 Adaptive — gotowe komponenty (ListDetailScaffold) z automatyczną adaptacją

Opracujemy aplikację mobilną pod klucz

IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.

Omów projekt

Przeczytaj również