Adaptive Layout — interfeyslərin yığılmasına yanaşma, burada tətbiq müxtəlif cihaz növləri və ekran oriyentasiyaları üçün fərqli maketlər istifadə edir. Responsive design-dən fərqli olaraq, adaptive layout breakpoint nöqtələrində əvvəlcədən hazırlanmış layoutlar arasında keçid edir. Məqalə iOS-da Size Classes və UITraitCollection, Android-də sw600dp/layout-w600dp kvalifikatorlarını və praktiki nümunələri izah edir.
Əsas məqamlar
Adaptive Layout — interfeys qurma strategiyası, burada proqramçı hər cihaz növü üçün ayrı maketlər yaradır və ekran xüsusiyyətlərinə əsasən onlar arasında keçid edir. Responsive design-dən fərqli olaraq, burada elementlər axıcı şəkildə yerləşmir, əksinə diskret keçidlər istifadə olunur: telefon — bir layout, planşet — başqa, desktop — üçüncü.
Apple Size Classes-ı iOS 8-də (2014) universal tətbiqlərlə (həm iPhone, həm də iPad-də işləyən universal binaries) təqdim etdi. Google smallestWidth (sw
Müasir Apple HIG və Google Material Design tövsiyələri bir neçə ekran ölçüsünü dəstəkləyən tətbiqlər üçün adaptive layout-un məcburi olduğunu bildirir. Google Play planşetlərə uyğunlaşmanın olmamasını çatışmazlıq kimi qeyd edir. App Store-un rəsmi tələbi yoxdur, lakin iPad istifadəçiləri uzadılmış iPhone tətbiqi deyil, native təcrübə gözləyirlər.
Size Classes — iOS-un hər oxda mövcud məkanı kompakt (Compact) və ya regular (Regular) kimi təyin edən xüsusiyyəti. UITraitCollection — mühit xüsusiyyətlərini view iyerarxiyası ilə ötürmək üçün iOS sistemi: ölçü, ekran miqyası, force touch capability, rəng gamutu (light/dark), accessibility settings.
| Cihaz / Oriyentasiya | Horizontal | Vertical |
|---|---|---|
| iPhone portret (bütün modellər) | Compact | Regular |
| iPhone Plus/Pro Max landşaft | Regular | Compact |
| iPhone SE / mini landşaft | Compact | Compact |
| iPad portret | Regular | Regular |
| iPad landşaft | Regular | Regular |
| iPad Split View (1/3 ekran) | Compact | Regular |
UITraitCollection sistem tərəfindən yaradılır və UIApplication-dən UIWindow vasitəsilə hər UIView-ə ötürülür. Oriyentasiya və ya pəncərə ölçüsü dəyişdikdə (Split View) sistem yeni UITraitCollection yaradır və traitCollectionDidChange-i çağırır. SwiftUI-də dəyişikliklər Environment Values vasitəsilə izlənilir və view avtomatik yenidən çəkilir.
UIKit-də UITraitCollection vasitəsilə adaptasiya trait collection dəyişikliklərinə abunə olmaq və layout-u yenidən qurmaqla həyata keçirilir. Aşağıdakı nümunə iPhone üçün şaquli stack və iPad üçün split-maket arasında keçid edən kontrolleri göstərir.
// Size Classes ilə adaptiv kontroller
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: tab bar + 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
}
}traitCollectionDidChange metodu traits-də hər hansı dəyişiklik olduqda çağırılır, lakin biz yalnız horizontalSizeClass-ı yoxlayırıq — maket dəyişikliyi üçün ən əhəmiyyətli göstərici. iOS 17+-da dəyişikliklərə abunə olmadan reaktiv yeniləmə üçün UIViewController.horizontalSizeClass-dan computed property kimi istifadə etmək tövsiyə olunur.
Android iki əsas en kvalifikatoru təqdim edir: sw
| Kvalifikator | Şərt | Cihaz nümunəsi |
|---|---|---|
| layout-sw600dp | Minimum en ≥ 600 dp | iPad (768 dp), Pixel C (900 dp) |
| layout-w600dp | Cari en ≥ 600 dp | iPad Split View-də (600 dp), landşaftda telefon |
| layout-land | Landşaft oriyentasiyası | Üfüqi vəziyyətdə olan istənilən cihaz |
| layout-sw720dp | Minimum en ≥ 720 dp | iPad Pro (833 dp), Galaxy Tab S9 (800 dp) |
Android-də resurs iyerarxiyası: res/layout/activity_main.xml (telefon), res/layout-sw600dp/activity_main.xml (7" planşet), res/layout-w600dp-land/activity_main.xml (landşaftda telefon). Android cari cihaz konfiqurasiyasına uyğun ən spesifik kvalifikatoru seçir. Uyğun fayl olmadıqda, res/layout/-dan əsas resurs istifadə olunur.
Müasir Android inkişafı resursların birbaşa oxunması əvəzinə pəncərə ölçüsünü təyin etmək üçün Jetpack WindowManager istifadə edir. Bu, ekran ölçüsünün dinamik dəyişdiyi qatlanan cihazlar üçün xüsusilə vacibdir (Galaxy Fold 6,2"-dən 7,6"-yə açılır).
// Jetpack WindowManager vasitəsilə adaptasiya
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()
}
}androidx.window kitabxanasından currentWindowAdaptiveInfo() komponenti interfeysi istənilən cihaza uyğunlaşdırır: telefon, planşet, qatlanan, ChromeOS. En sinifləri: COMPACT (0–600 dp), MEDIUM (600–840 dp), EXPANDED (>840 dp). Hündürlük sinifləri: COMPACT (0–480 dp), MEDIUM (480–900 dp), EXPANDED (>900 dp).
SwiftUI Size Classes-dan birbaşa istifadə etmədən adaptiv layout üçün daxili alətlər təqdim edir. AnyLayout, ViewThatFits və GeometryReader mövcud məkana avtomatik uyğunlaşan interfeyslər qurmağa imkan verir.
// AnyLayout və ViewThatFits vasitəsilə SwiftUI adaptasiyası
import SwiftUI
struct AdaptiveGrid: View {
@Environment(\.horizontalSizeClass) private var hSizeClass
var body: some View {
let layout = hSizeClass == .regular
? AnyLayout(HStackLayout())
: AnyLayout(VStackLayout())
layout {
Label("Seçilmişlər", systemImage: "star")
Label("Son", systemImage: "clock")
Label("Parametrlər", systemImage: "gear")
}
.padding()
}
}
// ViewThatFits — layout-un avtomatik seçimi
struct SmartLayout: View {
var body: some View {
ViewThatFits {
HStack { // Prioritet 1: üfüqi
CardView()
CardView()
}
VStack { // Sığmazsa — şaquli
CardView()
CardView()
}
}
}
}ViewThatFits — SwiftUI-nin güclü aləti, kəsilmədən mövcud məkana sığan ilk uşaq view-i avtomatik seçir. Bu, sadə adaptiv keçidlər üçün Size Classes-ı açıq şəkildə yoxlamaq ehtiyacını aradan qaldırır. AnyLayout view-in gövdəsində if-else olmadan layout növünü (HStack/VStack) dəyişməyə imkan verir.
Jetpack Compose adaptiv düzən üçün WindowSizeClass (Material 3 adaptive kitabxanasından) və BoxWithConstraints istifadə edir. XML kvalifikatorlarından fərqli olaraq, Compose pəncərə ölçüsünü runtime-da təyin edir və konfiqurasiya dəyişdikdə UI-ni reaktiv şəkildə yenidən qurur.
// BoxWithConstraints vasitəsilə Jetpack Compose adaptasiyası
@Composable
fun AdaptiveList(items: List<String>) {
BoxWithConstraints {
val width = maxWidth
if (width >= 600.dp) {
// Planşet: iki sütun (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: naviqasiya ilə bir sütun
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 hazır komponentlər təqdim edir: ListDetailScaffold, SupportingPaneScaffold, NavigationSuiteScaffold. Bu komponentlər avtomatik olaraq WindowWidthSizeClass-a uyğunlaşır — stacked (COMPACT) və side-by-side (MEDIUM/EXPANDED) arasında keçid edir. Proqramçı yalnız nümunə seçməli və məzmun panellərini ötürməlidir.
Tez-tez verilən suallar
sw600dp (smallestWidth) — oriyentasiyadan asılı olmayaraq minimum ekran eni. Həmişə 7"+ planşetlərdə işləyir. w600dp (available width) — cari oriyentasiyanı nəzərə alan əlçatan en. Eni 600 dp-dən çox ola bilən landşaftdakı telefonlarda işləyir.
UITraitCollection — mühit xüsusiyyətlərini ehtiva edən iOS obyekti: size classes (horizontal/vertical), display scale, force touch capability, user interface idiom (iPhone/iPad), color gamut, accessibility settings. UIApplication-dan view iyerarxiyası ilə ötürülür və oriyentasiya və ya pəncərə ölçüsü dəyişdikdə yenilənir.
Jetpack WindowManager (Android) və UIScreen nativeBounds + traitCollection (iOS) istifadə edin. Qatlanan cihazlar açıldıqda Size Class-ı dəyişir. Activity-ni yenidən başlatmadan pəncərə ölçüsü dəyişikliklərinə reaktiv abunə ilə kanonik layout (list-detail, supporting pane) əsaslı arxitektura tövsiyə olunur.
Xeyr. Size Classes variasiyaları olan bir storyboard və ya UIKit vasitəsilə proqram layout istifadə edin. SwiftUI ümumiyyətlə storyboards tələb etmir. iPad üçün ayrı storyboards təkrarlama yaradır və dəstəyi çətinləşdirir — bir maketi Size Classes vasitəsilə uyğunlaşdırmaq daha yaxşıdır.
Canonical layouts — Google-dan hazır adaptiv düzən nümunələri: list-detail (siyahı + təfərrüat), supporting pane (məzmun + hərəkət paneli), feed (kartlar). Hər nümunə üç vəziyyəti dəstəkləyir (compact/medium/expanded) və Material 3 Adaptive kitabxanasında ListDetailScaffold və SupportingPaneScaffold vasitəsilə tətbiq edilmişdir.
Nəticə
Açar təslim mobil tətbiq hazırlayacağıq
IT Sectr 2017-ci ildən startaplar və bizneslər üçün iOS və Android tətbiqləri yaradır. Sizə məsləhət verəcəyik və ən yaxşı həlli təklif edəcəyik.
Həm də oxuyun