מידע בסיסי על הפריסה של הרכב

עם Jetpack Compose הרבה יותר קל לעצב ולפתח את ממשק המשתמש של האפליקציה. ‫Compose הופך את המצב לרכיבים בממשק המשתמש באמצעות:

  1. הרכב האלמנטים
  2. פריסת הרכיבים
  3. ציור של אלמנטים

פיתוח נייטיב: הפיכת המצב לממשק משתמש באמצעות קומפוזיציה, פריסה וציור

במסמך הזה נתמקד בפריסה של רכיבים, ונסביר על חלק מאבני הבניין ש-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, הפריסה של עץ ממשק המשתמש היא בסדר הבא:

  1. צומת הרמה הבסיסית (root) Row מתבקש למדוד.
  2. צומת הבסיס Row מבקש מהצאצא הראשון שלו, Image, לבצע מדידה.
  3. ‫Image הוא צומת עלה (כלומר, אין לו צאצאים), ולכן הוא מדווח על גודל ומחזיר הוראות מיקום.
  4. צומת הבסיס Row מבקש מהצאצא השני שלו, Column, לבצע מדידה.
  5. הצומת Column מבקש מהצאצא הראשון שלו Text למדוד.
  6. הצומת הראשון של Text הוא צומת עלה, ולכן הוא מדווח על גודל ומחזיר הוראות מיקום.
  7. הצומת Column מבקש מהצאצא השני שלו, Text, למדוד.
  8. הצומת השני של Text הוא צומת עלה, ולכן הוא מדווח על גודל ומחזיר הוראות מיקום.
  9. עכשיו, אחרי שהצומת Column מדד, קבע את הגודל ומיקם את הצאצאים שלו, הוא יכול לקבוע את הגודל והמיקום שלו.
  10. עכשיו, אחרי שצומת הבסיס 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),
        ) { /*...*/ }
    }
}

פריסה מורכבת עוד יותר, שמשתמשת ב-modifiers כדי לשנות את האופן שבו הגרפיקה מסודרת ואת האזורים שמגיבים לקלט של המשתמשים

בקוד שלמעלה, שימו לב לפונקציות השונות של משתני ה-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:

תרשים שמציג את המשבצות הזמינות בסרגל האפליקציה של Material Components

רכיבי Composable בדרך כלל מקבלים למבדה של רכיב Composable מסוג content ( content: @Composable () -> Unit). ממשקי API של מיקומים חושפים כמה פרמטרים של content לשימושים ספציפיים. לדוגמה, TopAppBar מאפשר לכם לספק את התוכן של title, navigationIcon ו-actions.

לדוגמה, Scaffold מאפשר לכם להטמיע ממשק משתמש עם מבנה הפריסה הבסיסי של Material Design. Scaffoldמספק משבצות לרכיבי Material הנפוצים ביותר ברמה העליונה, כמו TopAppBar, BottomAppBar, FloatingActionButton, ו-Drawer. באמצעות Scaffold, קל לוודא שהרכיבים האלה ממוקמים כמו שצריך ופועלים יחד בצורה נכונה.

האפליקציה לדוגמה JetNews, שמשתמשת ב-Scaffold כדי למקם כמה רכיבים

@Composable
fun HomeScreen(/*...*/) {
    ModalNavigationDrawer(drawerContent = { /* ... */ }) {
        Scaffold(
            topBar = { /*...*/ }
        ) { contentPadding ->
            // ...
        }
    }
}