أن تتيح أحجام شاشات مختلفة

يتيح التوافق مع أحجام الشاشات المختلفة إمكانية الوصول إلى تطبيقك من خلال أكبر مجموعة متنوعة من الأجهزة وأكبر عدد من المستخدمين.

لإتاحة أكبر عدد ممكن من أحجام العرض، سواء كانت شاشات أجهزة مختلفة أو نوافذ تطبيقات مختلفة في وضع النوافذ المتعددة، صمِّم تنسيقات تطبيقك لتكون متجاوبة وقابلة للتكيّف. توفّر التنسيقات المتجاوبة/القابلة للتكيّف تجربة محسّنة للمستخدم بغض النظر عن حجم الشاشة، ما يتيح لتطبيقك التوافق مع الهواتف والأجهزة اللوحية والأجهزة القابلة للطي وأجهزة ChromeOS والاتجاهات العمودية والأفقية وإعدادات العرض القابلة لتغيير الحجم، مثل وضع تقسيم الشاشة والعرض في نافذة.

تتغير التخطيطات المتجاوبة/التكيفية بناءً على مساحة العرض المتاحة. تتراوح التغييرات من تعديلات التخطيط الصغيرة التي تملأ المساحة (التصميم سريع الاستجابة) إلى استبدال تخطيط بالكامل بآخر حتى يتمكن تطبيقك من استيعاب أحجام العرض المختلفة بشكل أفضل (التصميم التكيفي).

بما أنّ Jetpack Compose هي مجموعة أدوات لواجهة المستخدم التعريفية، فهي مثالية لتصميم وتنفيذ التنسيقات التي تتغيّر ديناميكيًا لعرض المحتوى بشكل مختلف على أحجام الشاشات المختلفة.

توضيح التغييرات الكبيرة في التنسيق للدوال المركّبة على مستوى المحتوى

تشغل الدوال المركّبة على مستوى التطبيق والمحتوى كل مساحة العرض المتاحة لتطبيقك. بالنسبة إلى هذه الأنواع من الدوال المركّبة، قد يكون من المنطقي تغيير التنسيق العام لتطبيقك على الشاشات الكبيرة.

تجنَّب استخدام قيم الأجهزة المادية لاتخاذ قرارات بشأن التنسيق. قد يكون من المغري اتخاذ قرارات بناءً على قيمة ملموسة ثابتة (هل الجهاز جهاز لوحي؟ هل الشاشة الفعلية لها نسبة عرض إلى ارتفاع معينة؟)، ولكن الإجابات على هذه الأسئلة قد لا تكون مفيدة لتحديد المساحة المتاحة لواجهة المستخدم الخاصة بك.

الشكل 1: أشكال الأجهزة: الهواتف والأجهزة القابلة للطي والأجهزة اللوحية وأجهزة الكمبيوتر المحمول

على الأجهزة اللوحية، قد يكون التطبيق قيد التشغيل في وضع النوافذ المتعددة، ما يعني أنّ التطبيق قد يقسم الشاشة مع تطبيق آخر. في وضع العرض في نافذة أو على نظام التشغيل ChromeOS، قد يكون التطبيق في نافذة يمكن تغيير حجمها. قد يكون هناك أكثر من شاشة واحدة، مثل الأجهزة القابلة للطي. في كل هذه الحالات، لا يكون حجم الشاشة الفعلي مهمًا لتحديد كيفية عرض المحتوى.

بدلاً من ذلك، اتّخِذ قراراتك استنادًا إلى الجزء الفعلي من الشاشة المخصّص لتطبيقك والموضَّح في مقاييس النافذة الحالية التي توفّرها مكتبة WindowManager في Jetpack. للاطّلاع على مثال حول كيفية استخدام WindowManager في تطبيق Compose، يمكنك مراجعة نموذج JetNews.

يؤدي جعل التنسيقات متوافقة مع مساحة العرض المتاحة إلى تقليل مقدار المعالجة الخاصة اللازمة لدعم الأنظمة الأساسية مثل ChromeOS وعوامل الشكل مثل الأجهزة اللوحية والأجهزة القابلة للطي.

بعد تحديد مقاييس المساحة المتاحة لتطبيقك، يمكنك تحويل الحجم الأولي إلى فئة حجم نافذة كما هو موضّح في استخدام فئات حجم النافذة. فئات حجم النافذة هي نقاط توقُّف مصمَّمة لتحقيق التوازن بين بساطة منطق التطبيق والمرونة في تحسين تطبيقك ليناسب معظم أحجام الشاشات.

تشير فئات حجم النافذة إلى النافذة الإجمالية لتطبيقك، لذا استخدِم الفئات لاتخاذ قرارات التنسيق التي تؤثر في التنسيق العام لتطبيقك. يمكنك تمرير فئات حجم النافذة كحالة، أو يمكنك تنفيذ منطق إضافي لإنشاء حالة مشتقة لتمريرها إلى الدوال المركّبة المتداخلة.

@Composable
fun MyApp(
    windowSizeClass: WindowSizeClass = currentWindowAdaptiveInfo(supportLargeAndXLargeWidth = true).windowSizeClass
) {
    // Decide whether to show the top app bar based on window size class.
    val showTopAppBar = windowSizeClass.isHeightAtLeastBreakpoint(WindowSizeClass.HEIGHT_DP_MEDIUM_LOWER_BOUND)

    // MyScreen logic is based on the showTopAppBar boolean flag.
    MyScreen(
        showTopAppBar = showTopAppBar,
        /* ... */
    )
}

يؤدي النهج المتعدّد الطبقات إلى حصر منطق حجم العرض في موقع واحد بدلاً من توزيعه على تطبيقك في العديد من الأماكن التي يجب أن تظل متزامنة. ينتج عن موقع واحد حالة يمكن تمريرها بشكل صريح إلى دوال مركّبة أخرى تمامًا مثل أي حالة تطبيق أخرى. يؤدي تمرير الحالة بشكل صريح إلى تبسيط الدوال المركّبة الفردية لأنّها تأخذ فئة حجم النافذة أو الإعدادات المحدّدة مع بيانات أخرى.

الدوال المركّبة المتداخلة المرنة قابلة لإعادة الاستخدام

تكون الدوال المركّبة قابلة لإعادة الاستخدام بشكل أكبر عندما يمكن وضعها في مجموعة متنوعة من الأماكن. إذا كان يجب وضع دالة مركّبة في موقع جغرافي محدّد بحجم محدّد، من غير المرجّح أن تكون الدالة المركّبة قابلة لإعادة الاستخدام في سياقات أخرى. ويعني ذلك أيضًا أنّ الدوال المركّبة الفردية القابلة لإعادة الاستخدام يجب أن تتجنّب الاعتماد ضمنيًا على معلومات حجم العرض العالمية.

لنفترض أنّ لدينا دالة مركّبة متداخلة تنفّذ عرض على شكل قائمة مع تفاصيل، والذي قد يعرض إما لوحة واحدة أو لوحتَين جنبًا إلى جنب:

تطبيق يعرض لوحتَين جنبًا إلى جنب
الشكل 2. تطبيق يعرض تنسيقًا نموذجيًا لعرض على شكل قائمة مع تفاصيل: 1 هي منطقة القائمة، و2 هي منطقة التفاصيل.

يجب أن يكون قرار عرض على شكل قائمة مع تفاصيل جزءًا من التنسيق العام للتطبيق، لذا يتم تمرير القرار من دالة مركّبة على مستوى المحتوى:

@Composable
fun AdaptivePane(
    showOnePane: Boolean,
    /* ... */
) {
    if (showOnePane) {
        OnePane(/* ... */)
    } else {
        TwoPane(/* ... */)
    }
}

ماذا لو أردت بدلاً من ذلك أن تقوم دالة مركّبة بتغيير تخطيطها بشكل مستقل بناءً على مساحة العرض المتاحة، على سبيل المثال، بطاقة تعرض تفاصيل إضافية إذا سمحت المساحة بذلك؟ تريد تنفيذ بعض المنطق استنادًا إلى بعض أحجام العرض المتاحة، ولكن ما هو الحجم تحديدًا؟

الشكل 3. بطاقة ضيقة تعرض رمزًا وعنوانًا فقط، وبطاقة أوسع تعرض الرمز والعنوان ووصفًا قصيرًا.

تجنَّب محاولة استخدام حجم شاشة الجهاز الفعلية. لن يكون ذلك دقيقًا لأنواع الشاشات المختلفة، ولن يكون دقيقًا أيضًا إذا لم يكن التطبيق في وضع ملء الشاشة.

بما أنّ الدالة المركّبة ليست دالة مركّبة على مستوى المحتوى، لا تستخدِم مقاييس النافذة الحالية مباشرةً.

إذا تم وضع المكوّن مع مساحة متروكة (مثل المساحات المتروكة)، أو إذا كان التطبيق يتضمّن مكوّنات مثل أشرطة التنقّل أو أشرطة التطبيقات، قد يختلف مقدار مساحة العرض المتاحة للدالة المركّبة بشكل كبير عن المساحة الإجمالية المتاحة للتطبيق.

استخدِم العرض الذي يتم منحه فعليًا للدالة المركّبة لعرض نفسها. لديك خياران للحصول على هذا العرض:

  • إذا أردت تغيير مكان أو طريقة عرض المحتوى، استخدِم مجموعة من المُعدِّلات أو تنسيقًا مخصّصًا لجعل التنسيق متجاوبًا. قد يكون ذلك بسيطًا مثل جعل عنصر فرعي يملأ كل المساحة المتاحة، أو وضع عناصر فرعية في أعمدة متعددة إذا كانت هناك مساحة كافية.

  • إذا أردت تغيير ما تعرضه، استخدِم BoxWithConstraints كبديل أكثر فعالية. توفّر دالة BoxWithConstraints قيودًا للقياس يمكنك استخدامها لاستدعاء دوال مركّبة مختلفة استنادًا إلى مساحة العرض المتاحة. ومع ذلك، يؤدي ذلك إلى بعض التكاليف، لأنّ BoxWithConstraints يؤجّل عملية الإنشاء إلى مرحلة التنسيق، عندما تكون هذه القيود معروفة، ما يؤدي إلى تنفيذ المزيد من العمل أثناء التنسيق.

@Composable
fun Card(/* ... */) {
    BoxWithConstraints {
        if (maxWidth < 400.dp) {
            Column {
                Image(/* ... */)
                Title(/* ... */)
            }
        } else {
            Row {
                Column {
                    Title(/* ... */)
                    Description(/* ... */)
                }
                Image(/* ... */)
            }
        }
    }
}

إتاحة جميع البيانات لأحجام العرض المختلفة

عند تنفيذ دالة مركّبة تستفيد من مساحة العرض الإضافية، قد ترغب في أن تكون فعّالاً وتحمّل البيانات كتأثير جانبي لحجم العرض الحالي.

ومع ذلك، يتعارض هذا مع مبدأ تدفق البيانات أحادي الاتجاه، حيث يمكن رفع البيانات وتقديمها إلى الدوال المركّبة لعرضها بشكل مناسب. يجب توفير بيانات كافية للدالة المركّبة لكي يتوفّر لها دائمًا محتوى كافٍ لأي حجم شاشة، حتى إذا لم يتم استخدام جزء من المحتوى دائمًا.

@Composable
fun Card(
    imageUrl: String,
    title: String,
    description: String
) {
    BoxWithConstraints {
        if (maxWidth < 400.dp) {
            Column {
                Image(imageUrl)
                Title(title)
            }
        } else {
            Row {
                Column {
                    Title(title)
                    Description(description)
                }
                Image(imageUrl)
            }
        }
    }
}

استنادًا إلى مثال Card، يُرجى ملاحظة أنّه يتم دائمًا تمرير description إلى Card. على الرغم من أنّ السمة description لا تُستخدم إلا عندما يسمح العرض بعرضها، تتطلّب السمة Card دائمًا السمة description، بغض النظر عن العرض المتاح.

يؤدي تمرير المحتوى الكافي دائمًا إلى تبسيط التنسيقات التكيُّفية من خلال جعلها أقل اعتمادًا على الحالة وتجنُّب حدوث آثار جانبية عند التبديل بين أحجام العرض (التي قد تحدث بسبب تغيير حجم النافذة أو تغيير الاتجاه أو طي الجهاز وفتحه).

يسمح هذا المبدأ أيضًا بالحفاظ على الحالة في جميع تغييرات التنسيق. من خلال رفع المعلومات التي قد لا يتم استخدامها في جميع أحجام العرض، يمكنك الحفاظ على حالة التطبيق مع تغيُّر حجم التنسيق.

على سبيل المثال، يمكنك رفع علامة منطقية showMore بحيث يتم الحفاظ على حالة التطبيق عندما يؤدي تغيير حجم الشاشة إلى تبديل التنسيق بين إخفاء المحتوى وإظهاره:

@Composable
fun Card(
    imageUrl: String,
    title: String,
    description: String
) {
    var showMore by remember { mutableStateOf(false) }

    BoxWithConstraints {
        if (maxWidth < 400.dp) {
            Column {
                Image(imageUrl)
                Title(title)
            }
        } else {
            Row {
                Column {
                    Title(title)
                    Description(
                        description = description,
                        showMore = showMore,
                        onShowMoreToggled = { newValue ->
                            showMore = newValue
                        }
                    )
                }
                Image(imageUrl)
            }
        }
    }
}

مزيد من المعلومات

لمزيد من المعلومات حول التنسيقات التكيُّفية في Compose، يُرجى الاطّلاع على المراجع التالية:

نماذج التطبيقات

  • CanonicalLayouts هو مستودع لأنماط التصميم المُثبَتة التي توفّر تجربة مستخدم مثالية على الشاشات الكبيرة.
  • يوضّح تطبيق JetNews كيفية تصميم تطبيق يكيّف واجهة المستخدم الخاصة به للاستفادة من مساحة العرض المتاحة.
  • Reply هو نموذج تكيُّفي متوافق مع الأجهزة الجوّالة والأجهزة اللوحية والأجهزة القابلة للطي.
  • Now in Android هو تطبيق يستخدم تنسيقات تكيُّفية لإتاحة أحجام شاشات مختلفة.

الفيديوهات