Adaptive Layout: βασικές έννοιες, κλάσεις μεγέθους και χαρακτηριστικά

Συγγραφέας: IT Sectr Δημοσιεύτηκε: 2026-02-26 Χρόνος ανάγνωσης: 10 λεπ

Adaptive Layout — μια προσέγγιση στη σχεδίαση διεπαφών όπου η εφαρμογή χρησιμοποιεί διαφορετικές διατάξεις για διαφορετικούς τύπους συσκευών και προσανατολισμούς οθόνης. Σε αντίθεση με το responsive design, το adaptive layout εναλλάσσεται μεταξύ προκαθορισμένων διατάξεων σε σημεία breakpoint. Το άρθρο αναλύει τα Size Classes και το UITraitCollection στο iOS, τα χαρακτηριστικά sw600dp/layout-w600dp στο Android και παρέχει πρακτικά παραδείγματα.

Κύρια σημεία

  • Adaptive Layout — διακριτή εναλλαγή διατάξεων ανάλογα με το μέγεθος οθόνης, τον προσανατολισμό και τον τύπο συσκευής
  • Size Classes στο iOS ταξινομούν τον χώρο ως Compact ή Regular οριζόντια και κάθετα
  • UITraitCollection — δοχείο χαρακτηριστικών περιβάλλοντος iOS (μέγεθος, κλίμακα, force touch, χρωματική γκάμα)
  • sw600dp — χαρακτηριστικό ελάχιστου πλάτους Android για ταμπλέτες (7" και μεγαλύτερες)
  • layout-w600dp — χαρακτηριστικό διαθέσιμου πλάτους που ενεργοποιείται όταν υπάρχει αρκετός χώρος στον τρέχοντα προσανατολισμό

Τι είναι το Adaptive Layout;

Adaptive Layout — μια στρατηγική δημιουργίας διεπαφής όπου ο προγραμματιστής δημιουργεί ξεχωριστές διατάξεις για κάθε τύπο συσκευής και εναλλάσσεται μεταξύ τους με βάση τα χαρακτηριστικά της οθόνης. Σε αντίθεση με το responsive design, όπου τα στοιχεία ρέουν ομαλά, το adaptive layout χρησιμοποιεί διακριτές εναλλαγές: τηλέφωνο — μία διάταξη, ταμπλέτα — άλλη, desktop — τρίτη.

Η Apple εισήγαγε τα Size Classes στο iOS 8 (2014) μαζί με καθολικές εφαρμογές (universal binaries που λειτουργούν τόσο σε iPhone όσο και σε iPad). Η Google εισήγαγε το χαρακτηριστικό smallestWidth (swdp) στο Android 3.2 (2011). Και οι δύο μηχανισμοί λύνουν το ίδιο πρόβλημα: να δώσουν στον προγραμματιστή ένα εργαλείο για τη δημιουργία διαφορετικών UI για διαφορετικές οθόνες χωρίς διπλασιασμό κώδικα.

Οι σύγχρονες συστάσεις Apple HIG και Google Material Design συμφωνούν ότι το adaptive layout είναι υποχρεωτικό για εφαρμογές που υποστηρίζουν περισσότερα από ένα μεγέθη οθόνης. Το Google Play σηματοδοτεί την έλλειψη προσαρμογής για ταμπλέτες ως ελάττωμα. Το App Store δεν έχει τυπική απαίτηση, αλλά οι χρήστες iPad αναμένουν εγγενή εμπειρία, όχι μια τεντωμένη εφαρμογή iPhone.

Size Classes και UITraitCollection στο iOS

Size Classes — χαρακτηριστικό iOS που ορίζει τον διαθέσιμο χώρο ως Compact ή Regular σε κάθε άξονα. UITraitCollection — σύστημα iOS για τη μετάδοση χαρακτηριστικών περιβάλλοντος μέσω της ιεραρχίας view: μέγεθος, κλίμακα οθόνης, force touch capability, χρωματική γκάμα (light/dark), accessibility settings.

Συσκευή / ΠροσανατολισμόςHorizontalVertical
iPhone κατακόρυφο (όλα τα μοντέλα)CompactRegular
iPhone Plus/Pro Max οριζόντιοRegularCompact
iPhone SE / mini οριζόντιοCompactCompact
iPad κατακόρυφοRegularRegular
iPad οριζόντιοRegularRegular
iPad Split View (1/3 οθόνης)CompactRegular

Το UITraitCollection δημιουργείται από το σύστημα και μεταδίδεται από το UIApplication μέσω του UIWindow σε κάθε UIView. Κατά την αλλαγή προσανατολισμού ή μεγέθους παραθύρου (Split View), το σύστημα δημιουργεί ένα νέο UITraitCollection και καλεί το traitCollectionDidChange. Στο SwiftUI, οι αλλαγές παρακολουθούνται μέσω Environment Values και ανασχεδιάζουν αυτόματα το view.

Παράδειγμα προσαρμοστικής διάταξης σε Swift

Η προσαρμογή μέσω UITraitCollection στο UIKit υλοποιείται με εγγραφή στις αλλαγές trait collection και ανακατασκευή της διάταξης. Το παρακάτω παράδειγμα δείχνει έναν ελεγκτή που εναλλάσσεται μεταξύ κάθετης στοίβας για iPhone και split διάταξης για iPad.

swift
// Προσαρμοστικός ελεγκτής με 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: πλευρική γραμμή + περιεχόμενο
        } else {
            showCompactLayout() // iPhone: γραμμή καρτελών + 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 καλείται σε κάθε αλλαγή traits, αλλά ελέγχουμε μόνο το horizontalSizeClass — τον πιο σημαντικό δείκτη για αλλαγή διάταξης. Στο iOS 17+ συνιστάται η χρήση του UIViewController.horizontalSizeClass ως computed property για αντιδραστική ενημέρωση χωρίς εγγραφή στις αλλαγές.

Χαρακτηριστικά Android: sw600dp και w600dp

Το Android παρέχει δύο κύρια χαρακτηριστικά πλάτους: swdp (smallestWidth) και wdp (available width). Η διαφορά είναι κρίσιμη: sw — ελάχιστο πλάτος οθόνης ανεξάρτητα από προσανατολισμό, w — διαθέσιμο πλάτος λαμβάνοντας υπόψη τον τρέχοντα προσανατολισμό. Το sw εγγυάται ότι η διάταξη θα φορτωθεί για μια συσκευή που έχει φυσικά το καθορισμένο ελάχιστο πλάτος. Το w αντιδρά στην περιστροφή — σε οριζόντια λειτουργία μπορεί να ενεργοποιήσει το w600dp, ακόμα κι αν σε κατακόρυφη το πλάτος είναι 360 dp.

ΧαρακτηριστικόΣυνθήκηΠαράδειγμα συσκευής
layout-sw600dpΕλάχιστο πλάτος ≥ 600 dpiPad (768 dp), Pixel C (900 dp)
layout-w600dpΤρέχον πλάτος ≥ 600 dpiPad σε Split View (600 dp), τηλέφωνο σε οριζόντιο
layout-landΟριζόντιος προσανατολισμόςΟποιαδήποτε συσκευή γυρισμένη οριζόντια
layout-sw720dpΕλάχιστο πλάτος ≥ 720 dpiPad Pro (833 dp), Galaxy Tab S9 (800 dp)

Ιεραρχία πόρων στο Android: res/layout/activity_main.xml (τηλέφωνο), res/layout-sw600dp/activity_main.xml (ταμπλέτα 7"), res/layout-w600dp-land/activity_main.xml (τηλέφωνο σε οριζόντιο). Το Android επιλέγει το πιο συγκεκριμένο χαρακτηριστικό που ταιριάζει στην τρέχουσα διαμόρφωση της συσκευής. Ελλείψει κατάλληλου αρχείου, χρησιμοποιείται ο βασικός πόρος από το res/layout/.

Παράδειγμα προσαρμοστικής διάταξης σε Kotlin

Η σύγχρονη ανάπτυξη Android χρησιμοποιεί το Jetpack WindowManager για τον προσδιορισμό του μεγέθους παραθύρου αντί για άμεση ανάγνωση πόρων. Αυτό είναι ιδιαίτερα σημαντικό για αναδιπλούμενες συσκευές, όπου το μέγεθος οθόνης μπορεί να αλλάξει δυναμικά (Galaxy Fold ανοίγει από 6,2" σε 7,6").

kotlin
// Προσαρμογή μέσω 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("Τηλέφωνο", 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()
    }
}

Το στοιχείο currentWindowAdaptiveInfo() από τη βιβλιοθήκη androidx.window προσαρμόζει τη διεπαφή σε οποιαδήποτε συσκευή: τηλέφωνο, ταμπλέτα, αναδιπλούμενη, ChromeOS. Κλάσεις πλάτους: COMPACT (0–600 dp), MEDIUM (600–840 dp), EXPANDED (>840 dp). Κλάσεις ύψους: COMPACT (0–480 dp), MEDIUM (480–900 dp), EXPANDED (>900 dp).

Adaptive Layout στο SwiftUI

SwiftUI παρέχει ενσωματωμένα εργαλεία για προσαρμοστική διάταξη χωρίς άμεση χρήση Size Classes. Τα AnyLayout, ViewThatFits και GeometryReader επιτρέπουν τη δημιουργία διεπαφών που προσαρμόζονται αυτόματα στον διαθέσιμο χώρο.

swift
// Προσαρμογή SwiftUI μέσω AnyLayout και 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("Αγαπημένα", systemImage: "star")
            Label("Πρόσφατα", systemImage: "clock")
            Label("Ρυθμίσεις", systemImage: "gear")
        }
        .padding()
    }
}

// ViewThatFits — αυτόματη επιλογή διάταξης
struct SmartLayout: View {
    var body: some View {
        ViewThatFits {
            HStack { // Προτεραιότητα 1: οριζόντια
                CardView()
                CardView()
            }
            VStack { // Αν δεν χωράει — κάθετη
                CardView()
                CardView()
            }
        }
    }
}

ViewThatFits — ένα ισχυρό εργαλείο SwiftUI που επιλέγει αυτόματα το πρώτο θυγατρικό view που χωράει στον διαθέσιμο χώρο χωρίς αποκοπή. Αυτό εξαλείφει την ανάγκη για ρητό έλεγχο Size Classes για απλές προσαρμοστικές εναλλαγές. AnyLayout επιτρέπει την αλλαγή τύπου διάταξης (HStack/VStack) χωρίς if-else στο σώμα του view.

Adaptive Layout στο Jetpack Compose

Jetpack Compose χρησιμοποιεί WindowSizeClass (από τη βιβλιοθήκη Material 3 adaptive) και BoxWithConstraints για προσαρμοστική διάταξη. Σε αντίθεση με τα XML χαρακτηριστικά, το Compose καθορίζει το μέγεθος παραθύρου κατά τον χρόνο εκτέλεσης και ανασυνθέτει αντιδραστικά το UI όταν αλλάζει η διαμόρφωση.

kotlin
// Προσαρμογή Jetpack Compose μέσω BoxWithConstraints
@Composable
fun AdaptiveList(items: List<String>) {
    BoxWithConstraints {
        val width = maxWidth

        if (width >= 600.dp) {
            // Ταμπλέτα: δύο στήλες (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 {
            // Τηλέφωνο: μία στήλη με πλοήγηση
            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 παρέχει έτοιμα στοιχεία: ListDetailScaffold, SupportingPaneScaffold, NavigationSuiteScaffold. Αυτά τα στοιχεία προσαρμόζονται αυτόματα στο WindowWidthSizeClass — εναλλάσσονται μεταξύ stacked (COMPACT) και side-by-side (MEDIUM/EXPANDED). Ο προγραμματιστής χρειάζεται μόνο να επιλέξει το μοτίβο και να μεταδώσει τα πάνελ περιεχομένου.

Συχνές Ερωτήσεις

Ποια είναι η διαφορά μεταξύ sw600dp και w600dp;

sw600dp (smallestWidth) — ελάχιστο πλάτος οθόνης ανεξάρτητα από προσανατολισμό. Λειτουργεί πάντα σε ταμπλέτες 7"+. w600dp (available width) — διαθέσιμο πλάτος λαμβάνοντας υπόψη τον τρέχοντα προσανατολισμό. Λειτουργεί σε τηλέφωνα σε οριζόντιο προσανατολισμό, όπου το πλάτος μπορεί να υπερβεί τα 600 dp.

Τι είναι το UITraitCollection στο iOS;

UITraitCollection — ένα αντικείμενο iOS που περιέχει χαρακτηριστικά περιβάλλοντος: size classes (horizontal/vertical), display scale, force touch capability, user interface idiom (iPhone/iPad), color gamut, accessibility settings. Μεταδίδεται από το UIApplication μέσω της ιεραρχίας view και ενημερώνεται όταν αλλάζει ο προσανατολισμός ή το μέγεθος παραθύρου.

Πώς να προσαρμόσω την εφαρμογή για αναδιπλούμενες συσκευές;

Χρησιμοποιήστε Jetpack WindowManager (Android) και UIScreen nativeBounds + traitCollection (iOS). Οι αναδιπλούμενες συσκευές αλλάζουν Size Class κατά το άνοιγμα. Συνιστάται αρχιτεκτονική βασισμένη σε κανονικές διατάξεις (list-detail, supporting pane) με αντιδραστική εγγραφή στις αλλαγές μεγέθους παραθύρου χωρίς επανεκκίνηση του Activity.

Χρειάζεται να δημιουργήσω ξεχωριστά storyboards για iPad;

Όχι. Χρησιμοποιήστε ένα storyboard με Size Classes μεταβολές constraint ή προγραμματιστική διάταξη μέσω UIKit. Το SwiftUI δεν απαιτεί καθόλου storyboards. Ξεχωριστά storyboards για iPad δημιουργούν διπλασιασμό και περιπλέκουν τη συντήρηση — είναι καλύτερο να προσαρμόσετε μία διάταξη μέσω Size Classes.

Τι είναι τα canonical layouts στο Material 3;

Canonical layouts — έτοιμα μοτίβα προσαρμοστικής διάταξης από την Google: list-detail (λίστα + λεπτομέρειες), supporting pane (περιεχόμενο + πίνακας ενεργειών), feed (κάρτες). Κάθε μοτίβο υποστηρίζει τρεις καταστάσεις (compact/medium/expanded) και υλοποιείται στη βιβλιοθήκη Material 3 Adaptive μέσω ListDetailScaffold και SupportingPaneScaffold.

Σύνοψη

  • Adaptive Layout — διακριτή εναλλαγή διατάξεων σε σημεία breakpoint για διαφορετικές συσκευές
  • Size Classes iOS (Compact/Regular) καθορίζουν τη διάταξη για iPhone, iPad και Split View
  • UITraitCollection — μηχανισμός μετάδοσης χαρακτηριστικών περιβάλλοντος μέσω της ιεραρχίας view
  • sw600dp — χαρακτηριστικό ελάχιστου πλάτους Android για ταμπλέτες 7"+
  • w600dp — χαρακτηριστικό διαθέσιμου πλάτους που ενεργοποιείται κατά την περιστροφή σε οριζόντιο
  • Jetpack WindowManager παρέχει WindowSizeClass (WindowWidthSizeClass: COMPACT/MEDIUM/EXPANDED)
  • Material 3 Adaptive — έτοιμα στοιχεία με αυτόματη προσαρμογή (ListDetailScaffold)

Θα αναπτύξουμε μια εφαρμογή για κινητά έτοιμη για χρήση

Η IT Sectr δημιουργεί εφαρμογές iOS και Android για νεοφυείς επιχειρήσεις και επιχειρήσεις από το 2017. Θα σας συμβουλεύσουμε και θα προτείνουμε την καλύτερη λύση.

Συζήτηση έργου

Διαβάστε επίσης