תמיכה בגדלים שונים של מסכים מאפשרת גישה לאפליקציה במגוון רחב של מכשירים ובמספר הגדול ביותר של משתמשים.
כדי לתמוך בגודל מסך רב ככל האפשר – מסכים שונים במכשירים או חלונות שונים של אפליקציות במצב חלונות מרובים – כדאי לעצב את הפריסות של האפליקציה כך שיהיו רספונסיביות ומותאמות. פריסות רספונסיביות/אדפטיביות מספקות חוויית משתמש אופטימלית ללא קשר לגודל המסך, ומאפשרות לאפליקציה להתאים לטלפונים, לטאבלטים, למכשירים מתקפלים, למכשירי ChromeOS, לכיוונים לאורך ולרוחב ולתצורות מסך שניתן לשנות את הגודל שלהן, כמו מצב מסך מפוצל ותצוגת חלון במחשב.
פריסות רספונסיביות/אדפטיביות משתנות בהתאם למרחב הזמין בתצוגה. השינויים יכולים להיות החל מכוונונים קטנים של הפריסה כדי למלא את כל המרחב (עיצוב רספונסיבי) ועד החלפה מלאה של פריסה אחת בפריסה אחרת כדי שהאפליקציה תתאים בצורה הטובה ביותר למסכים בגדלים שונים (עיצוב אדפטיבי).
Jetpack 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(/* ... */) } }
מה קורה אם רוצים שהפריסה של רכיב ה-Composable תשתנה באופן עצמאי בהתאם למרחב הזמין במסך, למשל כרטיס שבו מוצגים פרטים נוספים אם יש מספיק מקום? אתם רוצים לבצע פעולה לוגית על סמך גודל מסך זמין כלשהו, אבל איזה גודל בדיוק?
מומלץ להימנע משימוש בגודל המסך בפועל של המכשיר. המידע הזה לא יהיה מדויק לגבי סוגים שונים של מסכים, וגם לא יהיה מדויק אם האפליקציה לא מוצגת במסך מלא.
מכיוון שהרכיב הניתן להתאמה אישית הוא לא רכיב מותאם אישית ברמת התוכן, אין להשתמש ישירות במדדים של החלון הנוכחי. אם הרכיב ממוקם עם רווח (למשל עם תוכן מוטמע), או אם האפליקציה כוללת רכיבים כמו מסילות ניווט או שורת סרגל האפליקציה, ייתכן שהמרחב הזמין להצגת הרכיב המודפס יהיה שונה באופן משמעותי מהמרחב הכולל שזמין לאפליקציה.
להשתמש ברוחב שהרכיב המודולרי מקבל בפועל כדי להציג אותו. יש שתי אפשרויות להשיג את הרוחב הזה:
אם רוצים לשנות את המיקום או את האופן שבו התוכן מוצג, אפשר להשתמש באוסף של משתני אופן הצגה או בפריסה בהתאמה אישית כדי שהפריסה תהיה רספונסיבית. אפשר לעשות זאת בצורה פשוטה, למשל, על ידי מילוי כל המרחב הזמין על ידי רכיב צאצא, או על ידי פריסה של רכיבי הצאצאים בכמה עמודות אם יש מספיק מקום.
אם רוצים לשנות את מה מוצג, אפשר להשתמש ב-
BoxWithConstraints
כחלופה יעילה יותר.BoxWithConstraints
מספק אילוצים למדידת שאפשר להשתמש בהם כדי להפעיל רכיבים מורכבים שונים על סמך שטח התצוגה הזמין. עם זאת, יש לכך מחיר מסוים, כיBoxWithConstraints
דוחה את היצירה עד לשלב הפריסה, כשהמגבלות האלה ידועות, וכתוצאה מכך צריך לבצע יותר עבודה במהלך הפריסה.
@Composable fun Card(/* ... */) { BoxWithConstraints { if (maxWidth < 400.dp) { Column { Image(/* ... */) Title(/* ... */) } } else { Row { Column { Title(/* ... */) Description(/* ... */) } Image(/* ... */) } } } }
חשוב לוודא שכל הנתונים זמינים לגודלי מסך שונים
כשמטמיעים רכיב מורכב שמנצל את שטח התצוגה הנוסף, יכול להיות שתתפתו להיות יעילים ולטעון נתונים כתוצאה משינויים בגודל התצוגה הנוכחי.
עם זאת, הפעולה הזו מנוגדת לעיקרון של תעבורת נתונים חד-כיוונית, שבה אפשר להעביר נתונים ולספק אותם לרכיבים הניתנים לקישור כדי לבצע עיבוד גרפי מתאים. צריך לספק מספיק נתונים לרכיב ה-Composable כדי שתמיד יהיה בו מספיק תוכן לכל גודל מסך, גם אם חלק מהתוכן לא ישמש תמיד.
@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
משמש רק כשהרוחב מאפשר להציג אותו, תמיד נדרש description
ב-Card
, ללא קשר לרוחב הזמין.
העברת תוכן מספיק תמיד משפרת את הפריסות המותאמות אישית, כי היא מפחיתה את הצורך בשימוש במצבים (states) ומונעת הפעלת תופעות לוואי במעבר בין גדלי מסכים (שיכולות להתרחש בגלל שינוי גודל החלון, שינוי הכיוון או קיפול ופתיחה של המכשיר).
העיקרון הזה מאפשר גם לשמור את המצב במהלך שינויים בפריסה. כשמשתמשים בהעלאה (hoisting) של מידע שעשוי שלא לשמש בכל גדלי המסך, אפשר לשמור על מצב האפליקציה כשגודל הפריסה משתנה. לדוגמה, אפשר להציג דגל בוליאני 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 הוא אפליקציה שמשתמשת בפריסות מותאמות כדי לתמוך בגדלים שונים של מסכים
סרטונים