دعم أحجام مختلفة للشاشة

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

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

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

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

إجراء تغييرات كبيرة على التنسيقات للعناصر القابلة للتجميع على مستوى الشاشة

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

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

مخطّط بياني يعرض العديد من أشكال الأجهزة المختلفة، بما في ذلك الهاتف والجهاز القابل للطي والجهاز اللوحي والكمبيوتر المحمول
الشكل 1. أشكال الأجهزة، مثل الهواتف والأجهزة القابلة للطي والأجهزة اللوحية وأجهزة الكمبيوتر المحمول

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

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

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

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

@Composable
fun MyApp(
    windowSizeClass: WindowSizeClass = currentWindowAdaptiveInfo().windowSizeClass
) {
    // Perform logic on the size class to decide whether to show the top app bar.
    val showTopAppBar = windowSizeClass.windowHeightSizeClass != WindowHeightSizeClass.COMPACT

    // MyScreen knows nothing about window sizes, and performs logic based on a Boolean flag.
    MyScreen(
        showTopAppBar = showTopAppBar,
        /* ... */
    )
}

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

العناصر المرنة المُدمجة قابلة لإعادة الاستخدام

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

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

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

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

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

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

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

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

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

لذلك، يجب استخدام العرض الذي يتم منحه للعنصر القابل للتجميع لكي يتم عرضه. لدينا خياران للحصول على هذا العرض:

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

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

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

ويسمح هذا المبدأ أيضًا بالحفاظ على الحالة في جميع تغييرات التنسيق. من خلال رفع المعلومات التي قد لا يتم استخدامها في جميع الأحجام، يمكننا الحفاظ على حالة المستخدم مع تغيُّر حجم التنسيق. على سبيل المثال، يمكننا رفع علامة منطقية 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)
            }
        }
    }
}

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

لمزيد من المعلومات عن التنسيقات المخصّصة في ميزة "الإنشاء"، يمكنك الرجوع إلى المراجع التالية.

أمثلة على التطبيقات

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

الفيديوهات