إنشاء تنسيقات التكيُّف

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

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

إجراء تغييرات كبيرة على التصميم الفاضح على مستوى الشاشة

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

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

مخطَّط يُظهر العديد من أشكال الأجهزة المختلفة، مثل هاتف وجهاز قابل للطي وجهاز لوحي وكمبيوتر محمول

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

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

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

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

class MainActivity : ComponentActivity() {
    @OptIn(ExperimentalMaterial3WindowSizeClassApi::class)
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContent {
            val windowSizeClass = calculateWindowSizeClass(this)
            MyApp(windowSizeClass)
        }
    }
}
@Composable
fun MyApp(windowSizeClass: WindowSizeClass) {
    // Perform logic on the size class to decide whether to show
    // the top app bar.
    val showTopAppBar = windowSizeClass.heightSizeClass != WindowHeightSizeClass.Compact

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

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

الأجهزة القابلة للدمج المرنة القابلة لإعادة الاستخدام

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

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

لقطة شاشة تطبيق تعرض لوحتين جنبًا إلى جنب

لقطة شاشة لتطبيق يعرض تنسيقًا نموذجيًا/تفاصيل. 1 هو منطقة القائمة، و2 هي منطقة التفاصيل.

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

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

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

أمثلة على بطاقتين مختلفتين: بطاقة ضيقة تعرض رمزًا وعنوانًا فقط، وبطاقة أوسع تعرض الرمز والعنوان والوصف القصير

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

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

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

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

إذا أردت تغيير العناصر التي تعرضها، يمكنك استخدام 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)
            }
        }
    }
}

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

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

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

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

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