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 использует дискретные переключения: телефон — один layout, планшет — другой, десктоп — третий.

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-приложения.

iOS Size Classes и UITraitCollection

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.

Пример адаптивного layout на Swift

Адаптация через UITraitCollection в UIKit реализуется подпиской на изменения trait collection и перестроением layout. Пример ниже показывает контроллер, который переключается между вертикальным стеком для 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: 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 вызывается при любом изменении traits, но мы проверяем только horizontalSizeClass — наиболее значимый признак для смены макета. В iOS 17+ рекомендуется использовать UIViewController.horizontalSizeClass как computed property для реактивного обновления без подписки на изменения.

Android квалификаторы: sw600dp и w600dp

Android предоставляет два основных квалификатора ширины: swdp (smallestWidth) и wdp (available width). Разница критична: sw — минимальная ширина экрана независимо от ориентации, w — доступная ширина с учётом текущей ориентации. sw гарантирует, что layout загрузится для устройства, которое физически имеет указанную минимальную ширину. w реагирует на поворот — при ландшафте может сработать w600dp, даже если в портрете ширина 360 dp.

КвалификаторУсловиеПример устройства
layout-sw600dpМинимальная ширина ≥ 600 dpiPad (768 dp), Pixel C (900 dp)
layout-w600dpТекущая ширина ≥ 600 dpiPad в Split View (600 dp), телефон в ландшафте
layout-landОриентация landscapeЛюбое устройство повёрнуто горизонтально
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/.

Пример адаптивного 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 предоставляет встроенные инструменты для адаптивного layout без прямого использования 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 — автоматический выбор layout
struct SmartLayout: View {
    var body: some View {
        ViewThatFits {
            HStack { // Приоритет 1: горизонтальный
                CardView()
                CardView()
            }
            VStack { // Если не влезло — вертикальный
                CardView()
                CardView()
            }
        }
    }
}

ViewThatFits — мощный инструмент SwiftUI, автоматически выбирающий первый дочерний view, который помещается в доступное пространство без обрезки. Это избавляет от необходимости явно проверять Size Classes для простых адаптивных переключений. AnyLayout позволяет переключать тип layout (HStack/VStack) без if-else в теле view.

Adaptive Layout в Jetpack Compose

Jetpack Compose использует WindowSizeClass (из библиотеки Material 3 adaptive) и BoxWithConstraints для адаптивной вёрстки. В отличие от XML-квалификаторов, Compose определяет размер окна в runtime и перекомпоновывает UI реактивно при изменении конфигурации.

kotlin
// Jetpack Compose adaptive через 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 при раскладывании. Рекомендуется архитектура на основе канонических layout (list-detail, supporting pane) с реактивной подпиской на изменения размера окна без перезапуска Activity.

Нужно ли создавать отдельные storyboards для iPad?

Нет. Используйте один storyboard с Size Classes вариациями constraint или программный layout через 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) определяют layout для 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 года. Мы проконсультируем вас и предложим наилучшее решение.

Обсудить проект

Читайте также