Adaptive Layout: các khái niệm chính, lớp kích thước và bộ định tính

Tác giả: IT Sectr Đã đăng: 2026-02-26 Thời gian đọc: 10 phút

Adaptive Layout là một cách tiếp cận bố cục giao diện trong đó ứng dụng sử dụng các bố cục khác nhau cho các loại thiết bị và hướng màn hình khác nhau. Không giống như responsive design, adaptive layout chuyển đổi giữa các bố cục được chuẩn bị sẵn tại các điểm ngắt. Bài viết này phân tích Size Classes và UITraitCollection trong iOS, bộ định tính sw600dp/layout-w600dp trong Android và đưa ra các ví dụ thực tế.

Những điểm chính

  • Adaptive Layout — chuyển đổi bố cục rời rạc tùy theo kích thước màn hình, hướng và loại thiết bị
  • Size Classes trong iOS phân loại không gian là Compact hoặc Regular theo chiều ngang và chiều dọc
  • UITraitCollection — vùng chứa các đặc tính môi trường iOS (kích thước, tỷ lệ, force touch, dải màu)
  • sw600dp — bộ định tính chiều rộng tối thiểu Android cho máy tính bảng (7\" trở lên)
  • layout-w600dp — bộ định tính chiều rộng khả dụng được kích hoạt khi có đủ không gian ở hướng hiện tại

Adaptive Layout là gì?

Adaptive Layout là một chiến lược UI nơi nhà phát triển tạo các bố cục riêng biệt cho từng loại thiết bị và chuyển đổi giữa chúng dựa trên đặc điểm màn hình. Không giống như responsive design nơi các phần tử trôi chảy mượt mà, adaptive layout sử dụng chuyển đổi rời rạc: điện thoại — một bố cục, máy tính bảng — bố cục khác, máy tính để bàn — bố cục thứ ba.

Apple giới thiệu Size Classes trong iOS 8 (2014) cùng với các ứng dụng phổ quát (nhị phân chạy trên cả iPhone và iPad). Google giới thiệu bộ định tính smallestWidth (swdp) trong Android 3.2 (2011). Cả hai cơ chế đều giải quyết cùng một vấn đề: cung cấp cho nhà phát triển công cụ tạo UI khác nhau cho các màn hình khác nhau mà không trùng lặp mã.

Các hướng dẫn hiện đại của Apple HIG và Google Material Design đều đồng ý rằng adaptive layout là cần thiết cho các ứng dụng hỗ trợ nhiều hơn một kích thước màn hình. Google Play đánh dấu việc thiếu thích ứng với máy tính bảng là một nhược điểm. App Store không có yêu cầu chính thức, nhưng người dùng iPad mong đợi trải nghiệm gốc, không phải ứng dụng iPhone bị kéo giãn.

Size Classes và UITraitCollection trong iOS

Size Classes là một đặc tính iOS xác định không gian khả dụng là Compact (gọn) hoặc Regular (thông thường) trên mỗi trục. UITraitCollection là một hệ thống iOS để truyền các đặc tính môi trường qua hệ thống phân cấp view: kích thước, tỷ lệ màn hình, khả năng force touch, dải màu (sáng/tối), cài đặt trợ năng.

Thiết bị / HướngNgangDọc
iPhone dọc (tất cả các model)CompactRegular
iPhone Plus/Pro Max ngangRegularCompact
iPhone SE / mini ngangCompactCompact
iPad dọcRegularRegular
iPad ngangRegularRegular
iPad Split View (1/3 màn hình)CompactRegular

UITraitCollection được hệ thống tạo ra và truyền từ UIApplication qua UIWindow đến từng UIView. Khi hướng hoặc kích thước cửa sổ (Split View) thay đổi, hệ thống tạo UITraitCollection mới và gọi traitCollectionDidChange. Trong SwiftUI, các thay đổi được theo dõi qua Environment Values và view được vẽ lại tự động.

Ví dụ bố cục thích ứng trong Swift

Việc thích ứng qua UITraitCollection trong UIKit được thực hiện bằng cách đăng ký thay đổi trait collection và xây dựng lại bố cục. Ví dụ dưới đây cho thấy một bộ điều khiển chuyển đổi giữa ngăn xếp dọc cho iPhone và bố cục chia cho iPad.

swift
// Bộ điều khiển thích ứng với 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: thanh bên + nội dung
        } else {
            showCompactLayout() // iPhone: thanh tab + đẩy
        }
    }

    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
    }
}

Phương thức traitCollectionDidChange được gọi khi có bất kỳ thay đổi trait nào, nhưng chúng ta chỉ kiểm tra horizontalSizeClass — chỉ báo quan trọng nhất để chuyển đổi bố cục. Trong iOS 17+ nên sử dụng UIViewController.horizontalSizeClass như một thuộc tính tính toán để cập nhật phản ứng mà không cần đăng ký thay đổi.

Bộ định tính Android: sw600dp và w600dp

Android cung cấp hai bộ định tính chiều rộng chính: swdp (smallestWidth) và wdp (available width). Sự khác biệt rất quan trọng: sw là chiều rộng màn hình tối thiểu bất kể hướng, w là chiều rộng khả dụng có tính đến hướng hiện tại. sw đảm bảo bố cục được tải cho thiết bị có chiều rộng tối thiểu được chỉ định về mặt vật lý. w phản ứng với việc xoay — ở chế độ ngang, w600dp có thể được kích hoạt ngay cả khi chiều rộng ở chế độ dọc là 360 dp.

Bộ định tínhĐiều kiệnVí dụ thiết bị
layout-sw600dpChiều rộng tối thiểu ≥ 600 dpiPad (768 dp), Pixel C (900 dp)
layout-w600dpChiều rộng hiện tại ≥ 600 dpiPad ở Split View (600 dp), điện thoại ở chế độ ngang
layout-landHướng ngangBất kỳ thiết bị nào xoay ngang
layout-sw720dpChiều rộng tối thiểu ≥ 720 dpiPad Pro (833 dp), Galaxy Tab S9 (800 dp)

Hệ thống phân cấp tài nguyên trong Android: res/layout/activity_main.xml (điện thoại), res/layout-sw600dp/activity_main.xml (máy tính bảng 7\"), res/layout-w600dp-land/activity_main.xml (điện thoại ở chế độ ngang). Android chọn bộ định tính cụ thể nhất phù hợp với cấu hình thiết bị hiện tại. Khi không có tệp phù hợp, tài nguyên cơ sở từ res/layout/ được sử dụng.

Ví dụ bố cục thích ứng trong Kotlin

Phát triển Android hiện đại sử dụng Jetpack WindowManager để xác định kích thước cửa sổ thay vì đọc trực tiếp tài nguyên. Điều này đặc biệt quan trọng đối với các thiết bị có thể gập lại nơi kích thước màn hình thay đổi động (Galaxy Fold mở ra từ 6,2\" lên 7,6\").

kotlin
// Thích ứng qua 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("Điện thoại", 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()
    }
}

Thành phần currentWindowAdaptiveInfo() từ thư viện androidx.window thích ứng giao diện với mọi thiết bị: điện thoại, máy tính bảng, thiết bị gập, ChromeOS. Các lớp chiều rộng: COMPACT (0–600 dp), MEDIUM (600–840 dp), EXPANDED (>840 dp). Các lớp chiều cao: COMPACT (0–480 dp), MEDIUM (480–900 dp), EXPANDED (>900 dp).

Adaptive Layout trong SwiftUI

SwiftUI cung cấp các công cụ tích hợp cho bố cục thích ứng mà không cần sử dụng trực tiếp Size Classes. AnyLayout, ViewThatFits và GeometryReader cho phép xây dựng các giao diện tự động điều chỉnh theo không gian khả dụng.

swift
// Thích ứng SwiftUI qua AnyLayout và 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("Yêu thích", systemImage: "star")
            Label("Gần đây", systemImage: "clock")
            Label("Cài đặt", systemImage: "gear")
        }
        .padding()
    }
}

// ViewThatFits — chọn bố cục tự động
struct SmartLayout: View {
    var body: some View {
        ViewThatFits {
            HStack { // Ưu tiên 1: ngang
                CardView()
                CardView()
            }
            VStack { // Nếu không vừa — dọc
                CardView()
                CardView()
            }
        }
    }
}

ViewThatFits là một công cụ mạnh mẽ của SwiftUI tự động chọn view con đầu tiên vừa với không gian khả dụng mà không bị cắt. Điều này loại bỏ nhu cầu kiểm tra tường minh Size Classes cho các chuyển đổi thích ứng đơn giản. AnyLayout cho phép chuyển đổi loại bố cục (HStack/VStack) mà không cần if-else trong thân view.

Adaptive Layout trong Jetpack Compose

Jetpack Compose sử dụng WindowSizeClass (từ thư viện Material 3 adaptive) và BoxWithConstraints cho bố cục thích ứng. Không giống như các bộ định tính XML, Compose xác định kích thước cửa sổ tại thời điểm chạy và tổng hợp lại UI một cách phản ứng khi cấu hình thay đổi.

kotlin
// Jetpack Compose thích ứng qua BoxWithConstraints
@Composable
fun AdaptiveList(items: List<String>) {
    BoxWithConstraints {
        val width = maxWidth

        if (width >= 600.dp) {
            // Máy tính bảng: hai cột (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 {
            // Điện thoại: một cột với điều hướng
            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 cung cấp các thành phần có sẵn: ListDetailScaffold, SupportingPaneScaffold, NavigationSuiteScaffold. Các thành phần này tự động thích ứng với WindowWidthSizeClass — chuyển đổi giữa xếp chồng (COMPACT) và cạnh nhau (MEDIUM/EXPANDED). Nhà phát triển chỉ cần chọn một mẫu và truyền các bảng nội dung.

Câu hỏi thường gặp

Sự khác biệt giữa sw600dp và w600dp là gì?

sw600dp (smallestWidth) — chiều rộng màn hình tối thiểu bất kể hướng. Luôn kích hoạt trên máy tính bảng 7\"+. w600dp (available width) — chiều rộng khả dụng có tính đến hướng hiện tại. Kích hoạt trên điện thoại ở chế độ ngang nơi chiều rộng có thể vượt quá 600 dp.

UITraitCollection trong iOS là gì?

UITraitCollection là một đối tượng iOS chứa các đặc tính môi trường: size classes (ngang/dọc), tỷ lệ màn hình, khả năng force touch, ngữ cảnh giao diện người dùng (iPhone/iPad), dải màu, cài đặt trợ năng. Nó được truyền từ UIApplication qua hệ thống phân cấp view và thay đổi khi hướng hoặc kích thước cửa sổ thay đổi.

Làm thế nào để thích ứng ứng dụng với thiết bị gập?

Sử dụng Jetpack WindowManager (Android) và UIScreen nativeBounds + traitCollection (iOS). Thiết bị gập thay đổi Size Class khi mở ra. Kiến trúc dựa trên bố cục chuẩn (list-detail, supporting pane) với đăng ký phản ứng thay đổi kích thước cửa sổ mà không khởi động lại Activity được khuyến nghị.

Tôi có cần tạo storyboard riêng cho iPad không?

Không. Sử dụng một storyboard duy nhất với các biến thể ràng buộc Size Class hoặc bố cục lập trình qua UIKit. SwiftUI hoàn toàn không yêu cầu storyboard. Storyboard riêng cho iPad tạo ra sự trùng lặp và làm phức tạp việc bảo trì — tốt hơn nên thích ứng một bố cục duy nhất qua Size Classes.

Bố cục chuẩn (canonical layouts) trong Material 3 là gì?

Canonical layouts là các mẫu bố cục thích ứng có sẵn từ Google: list-detail (danh sách + chi tiết), supporting pane (nội dung + bảng hành động), feed (thẻ). Mỗi mẫu hỗ trợ ba trạng thái (compact/medium/expanded) và được triển khai trong thư viện Material 3 Adaptive qua ListDetailScaffold và SupportingPaneScaffold.

Tóm tắt

  • Adaptive Layout — chuyển đổi bố cục rời rạc tại các điểm ngắt cho các thiết bị khác nhau
  • Size Classes iOS (Compact/Regular) xác định bố cục cho iPhone, iPad và Split View
  • UITraitCollection — cơ chế truyền các đặc tính môi trường qua hệ thống phân cấp view
  • sw600dp — bộ định tính chiều rộng tối thiểu Android cho máy tính bảng 7\"+
  • w600dp — bộ định tính chiều rộng khả dụng được kích hoạt khi xoay sang chế độ ngang
  • Jetpack WindowManager cung cấp WindowSizeClass (WindowWidthSizeClass: COMPACT/MEDIUM/EXPANDED)
  • Material 3 Adaptive — thành phần có sẵn (ListDetailScaffold) với thích ứng tự động

Chúng tôi sẽ phát triển ứng dụng di động chìa khóa trao tay

IT Sectr tạo các ứng dụng iOS và Android cho các công ty khởi nghiệp và doanh nghiệp từ năm 2017. Chúng tôi sẽ tư vấn và đề xuất giải pháp tốt nhất cho bạn.

Thảo luận dự án

Đọc thêm