Adaptive Layout: แนวคิดหลัก คลาสขนาดและตัวระบุ

ผู้แต่ง: IT Sectr เผยแพร่เมื่อ: 2026-02-26 เวลาอ่าน: 10 นาที

Adaptive Layout เป็นแนวทางการจัดวางอินเทอร์เฟสที่แอปพลิเคชันใช้เลย์เอาต์ที่แตกต่างกันสำหรับอุปกรณ์ประเภทต่างๆ และการวางแนวหน้าจอที่แตกต่างกัน ซึ่งแตกต่างจาก responsive design ตรงที่ adaptive layout จะสลับระหว่างเลย์เอาต์ที่เตรียมไว้ล่วงหน้าที่จุดเบรกพอยต์ บทความนี้กล่าวถึง 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 เป็นกลยุทธ์ UI ที่นักพัฒนาสร้างเลย์เอาต์แยกต่างหากสำหรับอุปกรณ์แต่ละประเภทและสลับไปมาระหว่างกันตามคุณลักษณะของหน้าจอ ซึ่งแตกต่างจาก responsive design ที่องค์ประกอบต่างๆ ไหลลื่นอย่างต่อเนื่อง adaptive layout ใช้การสลับแบบไม่ต่อเนื่อง: โทรศัพท์ — เลย์เอาต์หนึ่ง แท็บเล็ต — อีกแบบ เดสก์ท็อป — แบบที่สาม

Apple เปิดตัว Size Classes ใน iOS 8 (2014) พร้อมกับแอปพลิเคชันสากล (ไบนารีที่ทำงานทั้งบน 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 ช่วงสี (สว่าง/มืด) การตั้งค่าการเข้าถึง

อุปกรณ์ / การวางแนวแนวนอนแนวตั้ง
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 และเลย์เอาต์แบบแบ่งสำหรับ 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: แถบแท็บ + ผลัก
        }
    }

    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 โดยอัตโนมัติ — สลับระหว่างแบบซ้อน (COMPACT) และแบบเคียงข้างกัน (MEDIUM/EXPANDED) นักพัฒนาเพียงแค่เลือกรูปแบบและส่งผ่านแผงเนื้อหา

คำถามที่พบบ่อย

ความแตกต่างระหว่าง sw600dp และ w600dp คืออะไร?

sw600dp (smallestWidth) — ความกว้างหน้าจอขั้นต่ำโดยไม่คำนึงถึงการวางแนว ทำงานบนแท็บเล็ต 7\"+ เสมอ w600dp (available width) — ความกว้างที่พร้อมใช้งานโดยพิจารณาจากการวางแนวปัจจุบัน ทำงานบนโทรศัพท์ในแนวนอนที่ความกว้างอาจเกิน 600 dp

UITraitCollection ใน iOS คืออะไร?

UITraitCollection คือออบเจกต์ iOS ที่มีคุณลักษณะสภาพแวดล้อม: size classes (แนวนอน/แนวตั้ง), มาตราส่วนหน้าจอ, ความสามารถ force touch, สำนวนอินเทอร์เฟสผู้ใช้ (iPhone/iPad), ช่วงสี, การตั้งค่าการเข้าถึง มันถูกส่งผ่านจาก UIApplication ผ่านลำดับชั้นของ view และเปลี่ยนแปลงเมื่อการวางแนวหรือขนาดหน้าต่างเปลี่ยนไป

จะปรับแอปพลิเคชันสำหรับอุปกรณ์พับได้อย่างไร?

ใช้ Jetpack WindowManager (Android) และ UIScreen nativeBounds + traitCollection (iOS) อุปกรณ์พับได้เปลี่ยน Size Class เมื่อกางออก แนะนำให้ใช้สถาปัตยกรรมบนพื้นฐานของเลย์เอาต์มาตรฐาน (list-detail, supporting pane) พร้อมการสมัครรับการเปลี่ยนแปลงขนาดหน้าต่างแบบรีแอกทีฟโดยไม่ต้องรีสตาร์ท Activity

ฉันจำเป็นต้องสร้าง storyboard แยกต่างหากสำหรับ iPad หรือไม่?

ไม่ ใช้ storyboard เดียวกับรูปแบบข้อจำกัด Size Class หรือเลย์เอาต์แบบโปรแกรมผ่าน UIKit SwiftUI ไม่ต้องการ storyboard เลย storyboard แยกต่างหากสำหรับ 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 — การสลับเลย์เอาต์แบบไม่ต่อเนื่องที่จุดเบรกพอยต์สำหรับอุปกรณ์ต่างๆ
  • 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 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ

ปรึกษาโครงการ

อ่านเพิ่มเติม