עם Jetpack Compose הרבה יותר קל לעצב ולפתח את ממשק המשתמש של האפליקציה. Compose הופך את המצב לרכיבים בממשק המשתמש באמצעות:
- הרכב האלמנטים
- פריסת הרכיבים
- ציור של אלמנטים
במסמך הזה נתמקד בפריסה של רכיבים, ונסביר על חלק מאבני הבניין ש-Compose מספק כדי לעזור לכם לפרוס את הרכיבים בממשק המשתמש.
מטרות הפריסות בפיתוח נייטיב
להטמעה של מערכת הפריסה ב-Jetpack Compose יש שתי מטרות עיקריות:
- ביצועים גבוהים
- יכולת לכתוב בקלות פריסות בהתאמה אישית
היסודות של פונקציות קומפוזביליות
פונקציות קומפוזביליות הן אבני הבניין הבסיסיות של Compose. פונקציה קומפוזבילית היא פונקציה שפולטת Unit שמתאר חלק כלשהו בממשק המשתמש. הפונקציה מקבלת קלט מסוים ויוצרת את מה שמוצג במסך. למידע נוסף על פונקציות קומפוזביליות, עיינו במסמכי התיעוד של המודל המנטלי של Compose.
פונקציה קומפוזבילית יכולה להפיק כמה רכיבים בממשק המשתמש. אבל אם לא תספקו הנחיות לגבי האופן שבו הם צריכים להיות מסודרים, יכול להיות ש-Compose יסדר את הרכיבים בצורה שלא תאהבו. לדוגמה, הקוד הזה יוצר שני רכיבי טקסט:
@Composable fun ArtistCard() { Text("Alfred Sisley") Text("3 minutes ago") }
בלי הנחיות לגבי הסידור הרצוי, Compose יערום את רכיבי הטקסט אחד על השני, כך שלא יהיה אפשר לקרוא אותם:
ב-Compose יש אוסף של פריסות מוכנות לשימוש שיעזרו לכם לסדר את הרכיבים בממשק המשתמש, וקל להגדיר פריסות משלכם, שמתאימות יותר לצרכים שלכם.
רכיבי פריסה רגילים
במקרים רבים, אפשר פשוט להשתמש ברכיבי הפריסה הרגילים של Compose.
משתמשים ב-Column כדי להציב פריטים במסך באופן אנכי.
@Composable fun ArtistCardColumn() { Column { Text("Alfred Sisley") Text("3 minutes ago") } }
באופן דומה, משתמשים ב-Row כדי למקם פריטים במסך באופן אופקי. גם Column וגם Row תומכים בהגדרת היישור של הרכיבים שהם מכילים.
@Composable fun ArtistCardRow(artist: Artist) { Row(verticalAlignment = Alignment.CenterVertically) { Image(bitmap = artist.image, contentDescription = "Artist image") Column { Text(artist.name) Text(artist.lastSeenOnline) } } }
משתמשים ב-Box כדי להציב רכיבים אחד מעל השני. ב-Box אפשר גם להגדיר יישור ספציפי של הרכיבים שהוא מכיל.
@Composable fun ArtistAvatar(artist: Artist) { Box { Image(bitmap = artist.image, contentDescription = "Artist image") Icon(Icons.Filled.Check, contentDescription = "Check mark") } }
הרבה פעמים אבני הבניין האלה הן כל מה שצריך. אתם יכולים לכתוב פונקציה קומפוזבילית משלכם כדי לשלב את הפריסות האלה בפריסה מורכבת יותר שמתאימה לאפליקציה שלכם.
כדי להגדיר את המיקום של הצאצאים בתוך Row, מגדירים את הארגומנטים horizontalArrangement ו-verticalAlignment. בשביל Column, מגדירים את הארגומנטים verticalArrangement ו-horizontalAlignment:
@Composable fun ArtistCardArrangement(artist: Artist) { Row( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.End ) { Image(bitmap = artist.image, contentDescription = "Artist image") Column { /*...*/ } } }
מודל הפריסה
במודל הפריסה, עץ ממשק המשתמש מוגדר במעבר אחד. כל צומת מתבקש קודם למדוד את עצמו, ואז למדוד את כל הצאצאים באופן רקורסיבי, ולהעביר את מגבלות הגודל במורד העץ לצאצאים. לאחר מכן, צומתי העלים מקבלים גודל ומיקום, והגודל והמיקום שנקבעו מועברים בחזרה במעלה העץ.
בקצרה, הורים מודדים לפני הילדים שלהם, אבל הגודל והמיקום שלהם נקבעים אחרי הילדים שלהם.
נבחן את הפונקציה הבאה של SearchResult.
@Composable fun SearchResult() { Row { Image( // ... ) Column { Text( // ... ) Text( // ... ) } } }
הפונקציה הזו מניבה את עץ ממשק המשתמש הבא.
SearchResult
Row
Image
Column
Text
Text
בדוגמה של SearchResult, הפריסה של עץ ממשק המשתמש היא בסדר הבא:
- צומת הרמה הבסיסית (root)
Rowמתבקש למדוד. - צומת הבסיס
Rowמבקש מהצאצא הראשון שלו,Image, לבצע מדידה. -
Imageהוא צומת עלה (כלומר, אין לו צאצאים), ולכן הוא מדווח על גודל ומחזיר הוראות מיקום. - צומת הבסיס
Rowמבקש מהצאצא השני שלו,Column, לבצע מדידה. - הצומת
Columnמבקש מהצאצא הראשון שלוTextלמדוד. - הצומת הראשון של
Textהוא צומת עלה, ולכן הוא מדווח על גודל ומחזיר הוראות מיקום. - הצומת
Columnמבקש מהצאצא השני שלו,Text, למדוד. - הצומת השני של
Textהוא צומת עלה, ולכן הוא מדווח על גודל ומחזיר הוראות מיקום. - עכשיו, אחרי שהצומת
Columnמדד, קבע את הגודל ומיקם את הצאצאים שלו, הוא יכול לקבוע את הגודל והמיקום שלו. - עכשיו, אחרי שצומת הבסיס
Rowמדד, קבע את הגודל ומיקם את הצאצאים שלו, הוא יכול לקבוע את הגודל והמיקום שלו.
ביצועים
כדי להשיג ביצועים גבוהים, ב-Compose מודדים את הצאצאים רק פעם אחת. מדידה במעבר יחיד טובה לביצועים, ומאפשרת ל-Compose לטפל ביעילות בעצי ממשק משתמש עמוקים. אם רכיב מסוים מודד את הרכיב הצאצא שלו פעמיים, והרכיב הצאצא הזה מודד כל אחד מהרכיבים הצאצאים שלו פעמיים וכן הלאה, ניסיון בודד לפרוס ממשק משתמש שלם ידרוש הרבה עבודה, ויהיה קשה לשמור על ביצועי האפליקציה.
אם מסיבה כלשהי צריך לבצע כמה מדידות בפריסה, ב-Compose יש מערכת מיוחדת שנקראת מדידות פנימיות. אפשר לקרוא מידע נוסף על התכונה הזו במאמר מדידות פנימיות בפריסות של Compose.
מכיוון שמדידה ומיקום הם שלבי משנה נפרדים של שלב הפריסה, אפשר לבצע בנפרד כל שינוי שמשפיע רק על מיקום הפריטים, ולא על המדידה.
שימוש במקשי צירוף בפריסות
כמו שמוסבר במאמר שינוי של רכיבים ב-Compose, אתם יכולים להשתמש ברכיבי שינוי כדי לקשט או לשפר את הרכיבים הניתנים לשינוי. משתני ה-modifier חיוניים להתאמה אישית של הפריסה. לדוגמה, כאן אנחנו משרשרים כמה משתני עיצוב כדי להתאים אישית את ArtistCard:
@Composable fun ArtistCardModifiers( artist: Artist, onClick: () -> Unit ) { val padding = 16.dp Column( Modifier .clickable(onClick = onClick) .padding(padding) .fillMaxWidth() ) { Row(verticalAlignment = Alignment.CenterVertically) { /*...*/ } Spacer(Modifier.size(padding)) Card( elevation = CardDefaults.cardElevation(defaultElevation = 4.dp), ) { /*...*/ } } }
בקוד שלמעלה, שימו לב לפונקציות השונות של משתני ה-modifier שמשמשות יחד.
-
clickableגורם לפונקציה ה-composable להגיב לקלט של המשתמש ומציג אפקט גלי. -
paddingמוסיף רווח מסביב לרכיב. fillMaxWidthגורם לרכיב ה-composable למלא את הרוחב המקסימלי שניתן לו מההורה שלו.- המאפיין
size()מציין את הרוחב והגובה המועדפים של רכיב.
פריסות עם גלילה
מידע נוסף על פריסות שניתן לגלול זמין במסמכי התיעוד של תנועות ב-Compose.
לרשימות ולרשימות עם טעינה מדורגת, עיינו במסמכי התיעוד של רשימות ב-Compose.
פריסות דינמיות
כשמעצבים פריסה, צריך להביא בחשבון כיווני מסך שונים וגדלים שונים של גורמי צורה. ב-Compose יש כמה מנגנונים מובנים שמאפשרים להתאים את הפריסות של הפונקציות הקומפוזביליות להגדרות תצורה שונות של המסך.
מגבלות
כדי לדעת מהן המגבלות שמגיעות מההורה ולעצב את הפריסה בהתאם, אפשר להשתמש ב-BoxWithConstraints. אילוצי המדידה נמצאים בהיקף של פונקציית הלמבדה של התוכן. אתם יכולים להשתמש באילוצי המדידה האלה כדי ליצור פריסות שונות להגדרות מסך שונות:
@Composable fun WithConstraintsComposable() { BoxWithConstraints { Text("My minHeight is $minHeight while my maxWidth is $maxWidth") } }
פריסות מבוססות-משבצות
ב-Compose יש מגוון גדול של רכיבים קומפוזביליים שמבוססים על Material Design עם יחסי התלות של androidx.compose.material:material (שנכללים כשיוצרים פרויקט Compose ב-Android Studio) כדי להקל על פיתוח ממשק המשתמש. רכיבים כמו Drawer, FloatingActionButton ו-TopAppBar מסופקים כולם.
רכיבי Material משתמשים הרבה בממשקי API של משבצות, דפוס ש-Compose מציג כדי להוסיף שכבה של התאמה אישית מעל רכיבים קומפוזביליים. הגישה הזו הופכת את הרכיבים לגמישים יותר, כי הם מקבלים רכיב צאצא שיכול להגדיר את עצמו, במקום לחשוף כל פרמטר הגדרה של הצאצא.
משבצות משאירות מקום ריק בממשק המשתמש, והמפתח יכול למלא אותו כרצונו. לדוגמה, אלה המיקומים שאפשר להתאים אישית ב-TopAppBar:
רכיבי Composable בדרך כלל מקבלים למבדה של רכיב Composable מסוג content ( content: @Composable
() -> Unit). ממשקי API של מיקומים חושפים כמה פרמטרים של content לשימושים ספציפיים.
לדוגמה, TopAppBar מאפשר לכם לספק את התוכן של title, navigationIcon ו-actions.
לדוגמה,
Scaffold
מאפשר לכם להטמיע ממשק משתמש עם מבנה הפריסה הבסיסי של Material Design.
Scaffoldמספק משבצות לרכיבי Material הנפוצים ביותר ברמה העליונה,
כמו TopAppBar,
BottomAppBar,
FloatingActionButton,
ו-Drawer. באמצעות Scaffold, קל לוודא שהרכיבים האלה ממוקמים כמו שצריך ופועלים יחד בצורה נכונה.
@Composable fun HomeScreen(/*...*/) { ModalNavigationDrawer(drawerContent = { /* ... */ }) { Scaffold( topBar = { /*...*/ } ) { contentPadding -> // ... } } }
מומלץ בשבילך
- הערה: טקסט הקישור מוצג כש-JavaScript מושבת
- מגדירי Compose
- Kotlin ל-Jetpack Compose
- רכיבים ופריסות של Material