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

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

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

تتغير التنسيقات المتجاوبة/التكيُّفية بناءً على مساحة العرض المتاحة. تتراوح

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

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

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

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

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

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

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

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

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

@OptIn(ExperimentalMaterial3AdaptiveApi::class)
@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,
        /* ... */
    )
}

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

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

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

ضع في الاعتبار المثال التالي: تخيل عنصرًا قابلاً للإنشاء متداخلاً ينفذ تخطيط list-detail، والذي قد يعرض جزءًا واحدًا أو جزءين جنبًا إلى جنب.

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

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

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

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

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

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

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

وبالتالي، علينا استخدام العرض الذي يتم منحه إلى العنصر القابل للإنشاء لعرض نفسه. لدينا خياران للحصول على هذا العرض:

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

إذا أردت تغيير ما تعرضه، يمكنك استخدام 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 دائمًا، بغض النظر عن العرض المتاح.

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

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

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

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

الفيديوهات الطويلة