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

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

لإتاحة أكبر عدد ممكن من أحجام الشاشات، سواء كانت شاشات أجهزة مختلفة أو نوافذ تطبيقات مختلفة في وضع النوافذ المتعددة، صمِّم تنسيقات تطبيقك لتكون متجاوبة وتكيفية. توفّر التصاميم السريعة الاستجابة/المتكيّفة تجربة مستخدم محسّنة بغض النظر عن حجم الشاشة، ما يتيح لتطبيقك أن يتوافق مع الهواتف والأجهزة اللوحية والأجهزة القابلة للطي وأجهزة 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 هو تطبيق يستخدم التصميمات المتجاوبة لإتاحة أحجام عرض مختلفة.

الفيديوهات