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 هي استراتيجية لبناء واجهة المستخدم حيث يُنشئ المطور تخطيطات منفصلة لكل نوع جهاز ويتبدل بينها بناءً على خصائص الشاشة. على عكس responsive design حيث تتدفق العناصر بسلاسة، يستخدم adaptive layout تبديلات متقطعة: الهاتف — تخطيط، الجهاز اللوحي — آخر، سطح المكتب — ثالث.

قدمت Apple Size Classes في iOS 8 (2014) مع التطبيقات الشاملة (الثنائيات التي تعمل على iPhone و iPad على حد سواء). قدمت Google مُؤهل smallestWidth (swdp) في Android 3.2 (2011). تحل كلتا الآليتين نفس المشكلة: إعطاء المطور أداة لإنشاء واجهات مستخدم مختلفة لشاشات مختلفة دون تكرار الكود.

تتفق إرشادات 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 كخاصية محسوبة للتحديث التفاعلي دون اشتراك في التغييرات.

مُؤهلات 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اتجاه 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/.

مثال تخطيط تكيفي بلغة 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 تختار تلقائيًا أول عرض فرعي يناسب المساحة المتاحة دون اقتطاع. هذا يلغي الحاجة إلى التحقق صراحة من Size Classes للتبديلات التكيفية البسيطة. AnyLayout يسمح بتبديل نوع التخطيط (HStack/VStack) دون if-else في جسم العرض.

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، idiom واجهة المستخدم (iPhone/iPad)، نطاق الألوان، إعدادات إمكانية الوصول. يُمرر من UIApplication عبر تسلسل الـ view ويتغير عند تغيير الاتجاه أو حجم النافذة.

كيف أُكيف التطبيق مع الأجهزة القابلة للطي؟

استخدم Jetpack WindowManager (Android) و UIScreen nativeBounds + traitCollection (iOS). تغير الأجهزة القابلة للطي Size Class عند الفتح. يُوصى ببنية تعتمد على التخطيطات الأساسية (list-detail, supporting pane) مع اشتراك تفاعلي في تغييرات حجم النافذة دون إعادة تشغيل Activity.

هل أحتاج إلى إنشاء storyboards منفصلة لـ iPad؟

لا. استخدم storyboard واحد مع تنويعات constraints حسب Size Class أو تخطيط برمجي عبر 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 — تبديل متقطع للتخطيطات عند نقاط الفصل لأجهزة مختلفة
  • 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. سوف نقدم لك النصح ونقترح أفضل حل.

مناقشة المشروع

اقرأ أيضًا