Adaptive Layout — egy interfész-készítési megközelítés, ahol az alkalmazás különböző elrendezéseket használ különböző típusú eszközökhöz és képernyő-tájolásokhoz. A responsive design-tól eltérően az adaptive layout előre elkészített elrendezések között vált a breakpoint pontokon. A cikk bemutatja a Size Classes-t és UITraitCollection-t iOS-ben, a sw600dp/layout-w600dp minősítőket Androidban, és gyakorlati példákat ad.
Főbb pontok
Adaptive Layout — egy interfész-építési stratégia, ahol a fejlesztő külön elrendezéseket hoz létre minden eszköztípushoz, és a képernyő jellemzői alapján vált közöttük. A responsive design-tól eltérően, ahol az elemek folyamatosan áramlanak, az adaptive layout diszkrét váltásokat használ: telefon — egy elrendezés, táblagép — másik, asztali — harmadik.
Az Apple bevezette a Size Classes-t az iOS 8-ban (2014) az univerzális alkalmazásokkal együtt (universal binaries, amelyek iPhone-on és iPad-en is működnek). A Google bevezette a smallestWidth (sw
A modern Apple HIG és Google Material Design ajánlások egyetértenek abban, hogy az adaptive layout kötelező az egynél több képernyőméretet támogató alkalmazásokhoz. A Google Play a táblagépekhez való adaptáció hiányát hibaként jelöli. Az App Store-nak nincs formális követelménye, de az iPad felhasználók natív élményt várnak, nem egy nyújtott iPhone alkalmazást.
Size Classes — iOS jellemző, amely a rendelkezésre álló teret Compact vagy Regular kategóriába sorolja minden tengelyen. UITraitCollection — iOS rendszer a környezeti jellemzők továbbítására a view hierarchián keresztül: méret, képernyő skála, force touch capability, színskála (light/dark), accessibility settings.
| Eszköz / Tájolás | Horizontal | Vertical |
|---|---|---|
| iPhone portré (minden modell) | Compact | Regular |
| iPhone Plus/Pro Max fekvő | Regular | Compact |
| iPhone SE / mini fekvő | Compact | Compact |
| iPad portré | Regular | Regular |
| iPad fekvő | Regular | Regular |
| iPad Split View (1/3 képernyő) | Compact | Regular |
Az UITraitCollection-t a rendszer hozza létre, és az UIApplication-től az UIWindow-n keresztül minden UIView-hoz továbbítja. A tájolás vagy ablakméret változásakor (Split View) a rendszer új UITraitCollection-t generál és meghívja a traitCollectionDidChange-et. SwiftUI-ban a változásokat az Environment Values követi nyomon, és automatikusan újrarajzolja a view-t.
Az adaptáció UITraitCollection segítségével UIKit-ben a trait collection változásokra való feliratkozással és az elrendezés újraépítésével valósul meg. Az alábbi példa egy kontrollert mutat, amely egy függőleges stack (iPhone) és egy split elrendezés (iPad) között vált.
// Adaptív kontroller Size Classes-szal
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: oldalsáv + tartalom
} else {
showCompactLayout() // iPhone: lapfülek + 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
}
}A traitCollectionDidChange metódus minden traits változáskor meghívódik, de mi csak a horizontalSizeClass-t ellenőrizzük — a legjelentősebb mutatót az elrendezésváltáshoz. iOS 17+ ajánlott a UIViewController.horizontalSizeClass használata computed property-ként a reaktív frissítéshez anélkül, hogy feliratkoznánk a változásokra.
Az Android két fő szélesség-minősítőt kínál: sw
| Minősítő | Feltétel | Eszköz példa |
|---|---|---|
| layout-sw600dp | Minimális szélesség ≥ 600 dp | iPad (768 dp), Pixel C (900 dp) |
| layout-w600dp | Aktuális szélesség ≥ 600 dp | iPad Split View-ben (600 dp), telefon fekvőben |
| layout-land | Fekvő tájolás | Bármely eszköz vízszintesen elforgatva |
| layout-sw720dp | Minimális szélesség ≥ 720 dp | iPad Pro (833 dp), Galaxy Tab S9 (800 dp) |
Erőforrás-hierarchia Androidban: res/layout/activity_main.xml (telefon), res/layout-sw600dp/activity_main.xml (7" táblagép), res/layout-w600dp-land/activity_main.xml (telefon fekvőben). Az Android kiválasztja a legspecifikusabb minősítőt, amely illeszkedik az aktuális eszközkonfigurációhoz. Megfelelő fájl hiányában az alap erőforrást használja a res/layout/-ból.
A modern Android fejlesztés a Jetpack WindowManager-t használja az ablakméret meghatározására ahelyett, hogy közvetlenül olvasná az erőforrásokat. Ez különösen fontos a hajlítható eszközök esetében, ahol a képernyő mérete dinamikusan változhat (Galaxy Fold 6,2"-ról 7,6"-ra nyílik).
// Adaptáció Jetpack WindowManager-en keresztül
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()
}
}A currentWindowAdaptiveInfo() komponens az androidx.window könyvtárból az interfészt bármilyen eszközhöz adaptálja: telefon, táblagép, hajlítható, ChromeOS. Szélességi osztályok: COMPACT (0–600 dp), MEDIUM (600–840 dp), EXPANDED (>840 dp). Magassági osztályok: COMPACT (0–480 dp), MEDIUM (480–900 dp), EXPANDED (>900 dp).
SwiftUI beépített eszközöket kínál az adaptív elrendezéshez a Size Classes közvetlen használata nélkül. Az AnyLayout, ViewThatFits és GeometryReader lehetővé teszik olyan interfészek építését, amelyek automatikusan alkalmazkodnak a rendelkezésre álló helyhez.
// SwiftUI adaptáció AnyLayout és ViewThatFits segítségével
import SwiftUI
struct AdaptiveGrid: View {
@Environment(\.horizontalSizeClass) private var hSizeClass
var body: some View {
let layout = hSizeClass == .regular
? AnyLayout(HStackLayout())
: AnyLayout(VStackLayout())
layout {
Label("Kedvencek", systemImage: "star")
Label("Legutóbbi", systemImage: "clock")
Label("Beállítások", systemImage: "gear")
}
.padding()
}
}
// ViewThatFits — automatikus elrendezésválasztás
struct SmartLayout: View {
var body: some View {
ViewThatFits {
HStack { // Prioritás 1: vízszintes
CardView()
CardView()
}
VStack { // Ha nem fér el — függőleges
CardView()
CardView()
}
}
}
}ViewThatFits — egy hatékony SwiftUI eszköz, amely automatikusan kiválasztja az első gyermek view-t, amely elfér a rendelkezésre álló helyen anélkül, hogy levágná. Ez kiküszöböli a Size Classes explicit ellenőrzésének szükségességét egyszerű adaptív váltásokhoz. AnyLayout lehetővé teszi az elrendezés típusának (HStack/VStack) megváltoztatását if-else nélkül a view törzsében.
Jetpack Compose a WindowSizeClass-t (a Material 3 adaptive könyvtárból) és a BoxWithConstraints-t használja az adaptív elrendezéshez. Az XML minősítőkkel ellentétben a Compose az ablakméretet futásidőben határozza meg, és reaktívan újraépíti az UI-t a konfiguráció változásakor.
// Jetpack Compose adaptáció BoxWithConstraints segítségével
@Composable
fun AdaptiveList(items: List<String>) {
BoxWithConstraints {
val width = maxWidth
if (width >= 600.dp) {
// Táblagép: két oszlop (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: egy oszlop navigációval
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() }
)
}
}
}
}A Material 3 Adaptive Layouts kész komponenseket kínál: ListDetailScaffold, SupportingPaneScaffold, NavigationSuiteScaffold. Ezek a komponensek automatikusan alkalmazkodnak a WindowWidthSizeClass-hoz — a stacked (COMPACT) és side-by-side (MEDIUM/EXPANDED) között váltanak. A fejlesztőnek csak a mintát kell kiválasztania és a tartalmi paneleket átadnia.
Gyakran Ismételt Kérdések
sw600dp (smallestWidth) — minimális képernyőszélesség tájolástól függetlenül. Mindig működik 7"+ táblagépeken. w600dp (available width) — rendelkezésre álló szélesség az aktuális tájolás figyelembevételével. Működik fekvő tájolású telefonokon, ahol a szélesség meghaladhatja a 600 dp-t.
UITraitCollection — egy iOS objektum, amely környezeti jellemzőket tartalmaz: size classes (horizontal/vertical), display scale, force touch capability, user interface idiom (iPhone/iPad), color gamut, accessibility settings. Az UIApplication-től a view hierarchián keresztül továbbítódik, és a tájolás vagy ablakméret változásakor frissül.
Használja a Jetpack WindowManager-t (Android) és az UIScreen nativeBounds + traitCollection-t (iOS). A hajlítható eszközök kinyitáskor megváltoztatják a Size Class-t. A kanonikus elrendezéseken (list-detail, supporting pane) alapuló architektúra ajánlott reaktív feliratkozással az ablakméret változásaira az Activity újraindítása nélkül.
Nem. Használjon egy storyboard-ot Size Classes constraint variációkkal vagy programozott elrendezést UIKit-en keresztül. A SwiftUI egyáltalán nem igényel storyboard-okat. Külön storyboard-ok az iPad-hez duplikációt hoznak létre és megnehezítik a karbantartást — jobb egy elrendezést adaptálni a Size Classes segítségével.
Canonical layouts — kész adaptív elrendezési minták a Google-tól: list-detail (lista + részletek), supporting pane (tartalom + műveleti panel), feed (kártyák). Minden minta három állapotot támogat (compact/medium/expanded), és a Material 3 Adaptive könyvtárban van implementálva a ListDetailScaffold és SupportingPaneScaffold segítségével.
Összefoglalás
Kulcsrakész mobilalkalmazást fejlesztünk
Az IT Sectr 2017 óta készít iOS és Android alkalmazásokat induló vállalkozásoknak és vállalkozásoknak. Tanácsot adunk, és a legjobb megoldást javasoljuk.
Olvassa el is