תמיכה בגודלי מסך שונים מאפשרת גישה לאפליקציה במגוון רחב של מכשירים ובמספר הגדול ביותר של משתמשים.
כדי לתמוך בכמה שיותר גדלי מסך, כדאי לתכנן את הפריסות של האפליקציה כך שיהיו רספונסיביות ומותאמות. פריסות רספונסיביות או דינמיות מספקות חוויית משתמש אופטימלית ללא קשר לגודל המסך, ומאפשרות לאפליקציה להתאים לשימוש בטלפונים, בטאבלטים, במכשירים מתקפלים, במכשירי ChromeOS, בכיוון לאורך ובכיוון לרוחב ובתצורות שניתן לשנות את הגודל שלהן, כמו מצב חלונות מרובים.
פריסות רספונסיביות/אדפטיביות משתנות בהתאם למרחב הזמין בתצוגה. השינויים יכולים להיות החל מכוונונים קטנים של הפריסה שממלאים את החלל (עיצוב רספונסיבי) ועד החלפה מלאה של פריסה אחת באחרת כדי שהאפליקציה תתאים בצורה הטובה ביותר למסכים בגדלים שונים (עיצוב אדפטיבי).
Jetpack Compose הוא ערכת כלים להצהרת ממשק משתמש, והוא אידיאלי לתכנון ולהטמעה של פריסות שמשתנות באופן דינמי כדי להציג תוכן בצורה שונה במגוון גדלים של מסכים.
ביצוע שינויים גדולים בפריסה של רכיבים מורכבים ברמת המסך באופן מפורש
כשמשתמשים ב-Compose כדי ליצור פריסה של אפליקציה שלמה, רכיבי ה-Composable ברמת האפליקציה וברמת המסך תופסים את כל המרחב שהאפליקציה מקבלת לצורך רינדור. ברמה הזו של העיצוב, יכול להיות שיהיה הגיוני לשנות את הפריסה הכוללת של המסך כדי לנצל את היתרונות של מסכים גדולים יותר.
אין להשתמש בערכים פיזיים של חומרה כדי לקבל החלטות לגבי הפריסה. יכול להיות שתתפתתו לקבל החלטות על סמך ערך מוחשי קבוע (המכשיר הוא טאבלט? האם למסך הפיזי יש יחס גובה-רוחב מסוים?), אבל התשובות לשאלות האלה לא תמיד יעזרו לכם לקבוע את המרחב שבו ממשק המשתמש יכול לפעול.
בטאבלטים, יכול להיות שאפליקציה פועלת במצב 'חלונות מרובים', כלומר שהיא מפצלת את המסך עם אפליקציה אחרת. ב-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 תשתנה באופן עצמאי על סמך המרחב הזמין? לדוגמה, כרטיס שרוצים להציג בו פרטים נוספים אם יש מספיק מקום. אנחנו רוצים לבצע פעולה לוגית על סמך גודל זמין כלשהו, אבל איזה גודל ספציפי?
כפי שראינו למעלה, מומלץ להימנע משימוש בגודל המסך בפועל של המכשיר. המידע הזה לא יהיה מדויק במספר מסכים, וגם לא יהיה מדויק אם האפליקציה לא מוצגת במסך מלא.
מכיוון שהרכיב הניתן לקישור הוא לא רכיב שניתן לקישור ברמת המסך, אנחנו גם לא צריכים להשתמש ישירות במדדי החלון הנוכחי כדי למקסם את האפשרות לשימוש חוזר. אם הרכיב ממוקם עם ריפוד (למשל, להוספת רכיבים פנימיים), או אם יש רכיבים כמו מסילות ניווט או שורת סרגל האפליקציה, נפח המקום שזמין לרכיב ה-Composable עשוי להיות שונה באופן משמעותי מהנפח הכולל שזמין לאפליקציה.
לכן, צריך להשתמש ברוחב שהרכיב הניתן לשילוב מקבל בפועל כדי להציג אותו. יש לנו שתי אפשרויות לקבל את הרוחב הזה:
אם רוצים לשנות את המיקום או את האופן שבו התוכן מוצג, אפשר להשתמש באוסף של משתני אופן הצגה או בפריסה בהתאמה אישית כדי שהפריסה תהיה רספונסיבית. אפשר לעשות זאת באופן פשוט, למשל, על ידי מילוי כל המרחב הזמין על ידי צאצא מסוים, או על ידי פריסה של הצאצאים בכמה עמודות אם יש מספיק מקום.
אם אתם רוצים לשנות את מה אתם מציגים, תוכלו להשתמש ב-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
משמש רק כשהרוחב מאפשר להציג אותו, אבל ה-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, תוכלו לעיין במקורות המידע הנוספים הבאים.
אפליקציות לדוגמה
- CanonicalLayouts הוא מאגר של דפוסי עיצוב מוכחים שמספקים חוויית משתמש אופטימלית במכשירים עם מסך גדול.
- JetNews מראה איך לעצב אפליקציה שמתאימה את ממשק המשתמש שלה כדי לנצל את המרחב הזמין
- Reply הוא דוגמה אדפטיבית לתמיכה בניידים, בטאבלטים ובמכשירים מתקפלים
- Now in Android היא אפליקציה שמשתמשת בפריסות מותאמות כדי לתמוך בגדלים שונים של מסכים
סרטונים
מומלץ עבורך
- הערה: טקסט הקישור מוצג כש-JavaScript מושבת
- מיפוי רכיבים לקוד קיים
- יסודות של יצירת פריסה
- שלבי Jetpack פיתוח נייטיב