Adaptive Layout: concepte cheie, clase de dimensiune și calificatori

Autor: IT Sectr Publicat: 2026-02-26 Timp de citire: 10 min

Adaptive Layout — abordare de realizare a interfețelor în care aplicația folosește diferite machete pentru diferite tipuri de dispozitive și orientări ale ecranului. Spre deosebire de responsive design, adaptive layout comută între machete pregătite în prealabil în punctele de breakpoint. Articolul analizează Size Classes și UITraitCollection în iOS, calificatorii sw600dp/layout-w600dp în Android și oferă exemple practice.

Principalele puncte

  • Adaptive Layout — comutarea discretă a machetelor în funcție de dimensiunea ecranului, orientare și tipul dispozitivului
  • Size Classes în iOS clasifică spațiul ca Compact sau Regular pe orizontală și verticală
  • UITraitCollection — container al caracteristicilor de mediu iOS (dimensiune, scară, force touch, gamă de culori)
  • sw600dp — calificatorul de lățime minimă Android pentru tablete (7" și mai mult)
  • layout-w600dp — calificatorul de lățime disponibilă care se activează când există suficient spațiu în orientarea curentă

Ce este Adaptive Layout?

Adaptive Layout — strategie de construire a interfeței în care dezvoltatorul creează machete separate pentru fiecare tip de dispozitiv și comută între ele pe baza caracteristicilor ecranului. Spre deosebire de responsive design, unde elementele curg lin, adaptive layout folosește comutări discrete: telefon — un layout, tabletă — altul, desktop — al treilea.

Apple a introdus Size Classes în iOS 8 (2014) împreună cu aplicațiile universale (universal binaries, care funcționează atât pe iPhone, cât și pe iPad). Google a introdus calificatorul smallestWidth (swdp) în Android 3.2 (2011). Ambele mecanisme rezolvă aceeași problemă: să ofere dezvoltatorului un instrument pentru a crea UI-uri diferite pentru ecrane diferite fără duplicarea codului.

Recomandările moderne Apple HIG și Google Material Design sunt de acord că adaptive layout este obligatoriu pentru aplicațiile care acceptă mai multe dimensiuni de ecran. Google Play marchează lipsa adaptării pentru tablete ca un defect. App Store nu are o cerință formală, dar utilizatorii de iPad se așteaptă la o experiență nativă, nu la o aplicație iPhone întinsă.

Size Classes și UITraitCollection în iOS

Size Classes — caracteristica iOS care definește spațiul disponibil ca Compact sau Regular pe fiecare axă. UITraitCollection — sistemul iOS pentru transmiterea caracteristicilor de mediu prin ierarhia view: dimensiune, scară ecran, force touch capability, gamă de culori (light/dark), accessibility settings.

Dispozitiv / OrientareHorizontalVertical
iPhone portret (toate modelele)CompactRegular
iPhone Plus/Pro Max peisajRegularCompact
iPhone SE / mini peisajCompactCompact
iPad portretRegularRegular
iPad peisajRegularRegular
iPad Split View (1/3 ecran)CompactRegular

UITraitCollection este format de sistem și transmis de la UIApplication prin UIWindow la fiecare UIView. La schimbarea orientării sau a dimensiunii ferestrei (Split View), sistemul generează un nou UITraitCollection și apelează traitCollectionDidChange. În SwiftUI, modificările sunt urmărite prin Environment Values și redesenează automat view-ul.

Exemplu de layout adaptiv în Swift

Adaptarea prin UITraitCollection în UIKit se implementează prin abonarea la modificările trait collection și reconstruirea layout-ului. Exemplul de mai jos arată un controler care comută între un stack vertical pentru iPhone și un layout split pentru iPad.

swift
// Controler adaptiv cu 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: bară laterală + conținut
        } else {
            showCompactLayout() // iPhone: bară de file + 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 este apelată la orice modificare a traits, dar verificăm doar horizontalSizeClass — cel mai semnificativ indicator pentru schimbarea machetei. În iOS 17+ se recomandă utilizarea UIViewController.horizontalSizeClass ca proprietate calculată pentru actualizare reactivă fără abonare la modificări.

Calificatorii Android: sw600dp și w600dp

Android oferă doi calificatori principali de lățime: swdp (smallestWidth) și wdp (available width). Diferența este critică: sw — lățimea minimă a ecranului independent de orientare, w — lățimea disponibilă ținând cont de orientarea curentă. sw garantează că layout-ul se va încărca pentru un dispozitiv care are fizic lățimea minimă specificată. w reacționează la rotire — în modul peisaj poate activa w600dp, chiar dacă în portret lățimea este de 360 dp.

CalificatorCondițieExemplu dispozitiv
layout-sw600dpLățime minimă ≥ 600 dpiPad (768 dp), Pixel C (900 dp)
layout-w600dpLățime curentă ≥ 600 dpiPad în Split View (600 dp), telefon în peisaj
layout-landOrientare peisajOrice dispozitiv rotit orizontal
layout-sw720dpLățime minimă ≥ 720 dpiPad Pro (833 dp), Galaxy Tab S9 (800 dp)

Ierarhia resurselor în Android: res/layout/activity_main.xml (telefon), res/layout-sw600dp/activity_main.xml (tabletă 7"), res/layout-w600dp-land/activity_main.xml (telefon în peisaj). Android alege cel mai specific calificator care se potrivește configurației curente a dispozitivului. În lipsa unui fișier potrivit, se folosește resursa de bază din res/layout/.

Exemplu de layout adaptiv în Kotlin

Dezvoltarea modernă Android folosește Jetpack WindowManager pentru a determina dimensiunea ferestrei în loc să citească direct resursele. Acest lucru este deosebit de important pentru dispozitivele pliabile, unde dimensiunea ecranului se poate schimba dinamic (Galaxy Fold se desface de la 6,2" la 7,6").

kotlin
// Adaptare prin 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()
    }
}

Componenta currentWindowAdaptiveInfo() din biblioteca androidx.window adaptează interfața la orice dispozitiv: telefon, tabletă, pliabil, ChromeOS. Clasele de lățime: COMPACT (0–600 dp), MEDIUM (600–840 dp), EXPANDED (>840 dp). Clasele de înălțime: COMPACT (0–480 dp), MEDIUM (480–900 dp), EXPANDED (>900 dp).

Adaptive Layout în SwiftUI

SwiftUI oferă instrumente încorporate pentru layout adaptiv fără utilizarea directă a Size Classes. AnyLayout, ViewThatFits și GeometryReader permit construirea de interfețe care se adaptează automat la spațiul disponibil.

swift
// Adaptare SwiftUI prin 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("Favorite", systemImage: "star")
            Label("Recent", systemImage: "clock")
            Label("Setări", systemImage: "gear")
        }
        .padding()
    }
}

// ViewThatFits — selecție automată a layout-ului
struct SmartLayout: View {
    var body: some View {
        ViewThatFits {
            HStack { // Prioritate 1: orizontal
                CardView()
                CardView()
            }
            VStack { // Dacă nu încape — vertical
                CardView()
                CardView()
            }
        }
    }
}

ViewThatFits — un instrument puternic SwiftUI care selectează automat primul view copil care încape în spațiul disponibil fără tăiere. Acest lucru elimină necesitatea de a verifica explicit Size Classes pentru comutări adaptive simple. AnyLayout permite schimbarea tipului de layout (HStack/VStack) fără if-else în corpul view-ului.

Adaptive Layout în Jetpack Compose

Jetpack Compose folosește WindowSizeClass (din biblioteca Material 3 adaptive) și BoxWithConstraints pentru layout adaptiv. Spre deosebire de calificatorii XML, Compose determină dimensiunea ferestrei în runtime și recompune UI reactiv la modificarea configurației.

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

        if (width >= 600.dp) {
            // Tabletă: două coloane (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: o coloană cu navigare
            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 oferă componente gata făcute: ListDetailScaffold, SupportingPaneScaffold, NavigationSuiteScaffold. Aceste componente se adaptează automat la WindowWidthSizeClass — comută între stacked (COMPACT) și side-by-side (MEDIUM/EXPANDED). Dezvoltatorul trebuie doar să selecteze modelul și să transmită panourile de conținut.

Întrebări frecvente

Care este diferența dintre sw600dp și w600dp?

sw600dp (smallestWidth) — lățimea minimă a ecranului independent de orientare. Se activează întotdeauna pe tabletele 7"+. w600dp (available width) — lățimea disponibilă ținând cont de orientarea curentă. Se activează pe telefoanele în peisaj, unde lățimea poate depăși 600 dp.

Ce este UITraitCollection în iOS?

UITraitCollection — obiect iOS care conține caracteristicile mediului: size classes (horizontal/vertical), display scale, force touch capability, user interface idiom (iPhone/iPad), color gamut, accessibility settings. Este transmis de la UIApplication prin ierarhia view și se modifică la schimbarea orientării sau a dimensiunii ferestrei.

Cum adaptez aplicația pentru dispozitive pliabile?

Folosește Jetpack WindowManager (Android) și UIScreen nativeBounds + traitCollection (iOS). Dispozitivele pliabile schimbă Size Class la desfacere. Se recomandă o arhitectură bazată pe layout-uri canonice (list-detail, supporting pane) cu abonare reactivă la modificările dimensiunii ferestrei fără repornirea Activity.

Este necesar să creez storyboards separate pentru iPad?

Nu. Folosește un singur storyboard cu variații Size Classes de constrângeri sau layout programatic prin UIKit. SwiftUI nu necesită deloc storyboards. Storyboards separate pentru iPad creează duplicare și complică întreținerea — este mai bine să adaptezi o singură machetă prin Size Classes.

Ce sunt canonical layouts în Material 3?

Canonical layouts — modele gata făcute de layout adaptiv de la Google: list-detail (listă + detalii), supporting pane (conținut + panou de acțiuni), feed (carduri). Fiecare model suportă trei stări (compact/medium/expanded) și este implementat în biblioteca Material 3 Adaptive prin ListDetailScaffold și SupportingPaneScaffold.

Rezumat

  • Adaptive Layout — comutarea discretă a machetelor în puncte de breakpoint pentru diferite dispozitive
  • Size Classes iOS (Compact/Regular) determină layout-ul pentru iPhone, iPad și Split View
  • UITraitCollection — mecanism de transmitere a caracteristicilor de mediu prin ierarhia view
  • sw600dp — calificatorul de lățime minimă Android pentru tablete 7"+
  • w600dp — calificatorul de lățime disponibilă care se activează la rotirea în peisaj
  • Jetpack WindowManager oferă WindowSizeClass (WindowWidthSizeClass: COMPACT/MEDIUM/EXPANDED)
  • Material 3 Adaptive — componente gata făcute cu adaptare automată (ListDetailScaffold)

Vom dezvolta o aplicație mobilă la cheie

IT Sectr creează aplicații iOS și Android pentru startup-uri și afaceri din 2017. Vă vom consilia și vă vom propune cea mai bună soluție.

Discutați proiectul

Citiți și