يتيح توافق تطبيقك مع أحجام الشاشات المختلفة إمكانية وصول أكبر عدد من المستخدمين على أوسع مجموعة من الأجهزة.
لتتوافق مع أكبر عدد ممكن من أحجام الشاشات، يجب تصميم تنسيقات تطبيقك لتكون سريعة الاستجابة وقابلة للتكيّف. توفّر التنسيقات السريعة الاستجابة/المتوافقة تجربة استخدام محسّنة بغض النظر عن حجم الشاشة، ما يتيح لتطبيقك استيعاب الهواتف والأجهزة اللوحية والأجهزة القابلة للطي وأجهزة ChromeOS ووضعَي الوضع العمودي والأفقي وإعدادات قابلة للتغيير مثل وضع المتعدّد المكوّنات.
تتغيّر التنسيقات المتجاوبة/المتوافقة استنادًا إلى مساحة العرض المتاحة. تتراوح التغييرات بين تعديلات التخطيط الصغيرة التي تملأ المساحة (التصميم المتجاوب) إلى استبدال تخطيط بآخر تمامًا حتى يتمكّن تطبيقك من استيعاب أحجام الشاشة المختلفة على أفضل نحو (التصميم التكيُّفي).
يشكّل Jetpack Compose مجموعة أدوات تعريفية لواجهة المستخدم، وهو مثالي لتصميم وتنفيذ تنسيقات تتغير ديناميكيًا لعرض المحتوى على مستوى مجموعة متنوعة من أحجام الشاشات.
إجراء تغييرات كبيرة على التنسيق للمحتوى القابل للإنشاء على مستوى الشاشة
عند استخدام أداة Compose لتصميم تطبيق كامل، تشغل العناصر القابلة للتجميع على مستوى التطبيق ومستوى الشاشة كل المساحة التي يحصل عليها تطبيقك لعرض المحتوى. في هذا المستوى من التصميم، قد يكون من المنطقي تغيير التنسيق العام للشاشة لمحاولة الاستفادة من الشاشات الأكبر حجمًا.
تجنَّب استخدام القيم المادية للأجهزة عند اتخاذ قرارات بشأن التنسيق. فقد يكون من المغري اتخاذ قرارات بناءً على قيمة ملموسة ثابتة (هل الجهاز جهاز لوحي؟ هل تتضمّن الشاشة الفعلية نسبة عرض إلى ارتفاع معيّنة؟)، ولكن قد لا تكون الإجابات عن هذه الأسئلة مفيدة لتحديد المساحة التي يمكن أن يعمل فيها واجهة المستخدم.
على الأجهزة اللوحية، قد يكون التطبيق قيد التشغيل في وضع "النوافذ المتعدّدة"، ما يعني أنّه قد يقسّم الشاشة مع تطبيق آخر. على نظام التشغيل 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, /* ... */ ) }
يحصر هذا النهج متعدد الطبقات منطق حجم الشاشة في موقع واحد، بدلاً من توزيعها عبر تطبيقك في العديد من الأماكن التي تحتاج إلى المزامنة. يُنشئ هذا الموقع الجغرافي الواحد حالة يمكن تمريرها صراحةً إلى العناصر القابلة للتجميع الأخرى تمامًا كما تفعل مع أي حالة أخرى للتطبيق. يؤدي تمرير الحالة صراحةً إلى تبسيط العناصر القابلة للتجميع الفردية، لأنّها ستكون وظائف قابلة للتجميع عادية تأخذ فئة الحجم أو الإعداد المحدّد مع البيانات الأخرى.
العناصر المرنة المُدمجة قابلة لإعادة الاستخدام
يمكن إعادة استخدام العناصر القابلة للتجميع بشكل أكبر عندما يمكن وضعها في مجموعة كبيرة من المواضع. إذا افترضَ عنصر قابل للإنشاء أنّه سيتم وضعه دائمًا في موقع معيّن وبحجم معيّن، سيكون من الصعب إعادة استخدامه في مكان آخر في موقع مختلف أو مع مساحة مختلفة متاحة. ويعني ذلك أيضًا أنّ العناصر القابلة للتجميع الفردية القابلة لإعادة الاستخدام يجب ألا تعتمد بشكل ضمني على معلومات الحجم "العمومي".
فكِّر في المثال التالي: تخيل عنصرًا مكوّنًا متداخلًا ينفِّذ تنسيقًا للتفاصيل في القائمة، قد يعرض لوحة واحدة أو لوحتَين جنبًا إلى جنب.
نريد أن يكون هذا القرار جزءًا من التنسيق العام للتطبيق، لذلك ننقل القرار من عنصر قابل للتجميع على مستوى الشاشة كما رأينا أعلاه:
@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) } } } }
مزيد من المعلومات
لمزيد من المعلومات عن التنسيقات المخصّصة في ميزة "الإنشاء"، يمكنك الرجوع إلى المراجع التالية.
أمثلة على التطبيقات
- CanonicalLayouts هو مستودع لنماذج التصميم التي أثبتت فعاليتها والتي توفّر تجربة مستخدم مثالية على الأجهزة ذات الشاشات الكبيرة.
- يوضّح تطبيق JetNews كيفية تصميم تطبيق يُعدّل واجهة المستخدم للاستفادة من المساحة المتوفّرة.
- الردّ: هو نموذج قابل للتكيّف للتوافق مع الأجهزة الجوّالة والأجهزة اللوحية والأجهزة القابلة للطي
- الآن على Android هو تطبيق يستخدم تصاميم تكيفية للتوافق مع أحجام الشاشات المختلفة.
الفيديوهات
أفلام مُقترَحة لك
- ملاحظة: يتم عرض نص الرابط عندما تكون لغة JavaScript غير مفعّلة.
- ربط المكوّنات بالرمز البرمجي الحالي
- أساسيات تنسيق الرسائل
- مراحل Jetpack Compose