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

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

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

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

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

  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 מודד את הצאצאים רק פעם אחת. מדידה ב-Single-pass טובה לביצועים, ומאפשרת ל-Compose לטפל ביעילות בעצים עמוקים של ממשק משתמש. אם רכיב מסוים מודד את הצאצא שלו פעמיים, והצאצא הזה מודד כל אחד מהצאצאים שלו פעמיים וכן הלאה, ניסיון אחד לתכנן פריסה של ממשק משתמש שלם ידרוש הרבה עבודה, ויהיה קשה לשמור על ביצועים טובים באפליקציה.

אם לפריסה שלכם דרושות כמה מדידות מסיבה כלשהי, Compose מציע מערכת מיוחדת, מדידות מובנות. מידע נוסף על התכונה הזו זמין במאמר מדידות מובנות בפריסות של Compose.

מכיוון שמדידה ומיקום הם שלבי משנה נפרדים של תהליך הבדיקה של הפריסה, אפשר לבצע בנפרד שינויים שמשפיעים רק על מיקום הפריטים ולא על המדידה.

שימוש במודיפיקטורים בפריסות

כפי שמוסבר בקטע שימוש במודיפיקרים ליצירת רכיבים מורכבים, אפשר להשתמש במודיפיקרים כדי לקשט או להוסיף רכיבים מורכבים. פונקציות השינוי חיוניות להתאמה אישית של הפריסה. לדוגמה, כאן אנחנו משלבים כמה משתני אופן כדי להתאים אישית את 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),
        ) { /*...*/ }
    }
}

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

בקוד שלמעלה, שימו לב לשימוש בפונקציות מודיפיקטור שונות יחד.

  • clickable מאפשר לרכיב הניתן ליצירה להגיב לקלט של המשתמש ולהציג תנודות.
  • padding יוצר רווח מסביב לאובייקט.
  • fillMaxWidth גורם לרכיב ה-Composable למלא את הרוחב המקסימלי שהוגדר לו על ידי ההורה שלו.
  • size() מציין את הרוחב והגובה המועדפים של רכיב.

פריסות שניתן לגלול בהן

מידע נוסף על פריסות שניתן לגלול בהן זמין במסמכי התיעוד של תנועות הכתיבה.

למידע נוסף על רשימות ורשימות עצלות, אפשר לעיין במסמכי העזרה בנושא כתיבת רשימות.

פריסות רספונסיביות

צריך לתכנן את הפריסה בהתאם לכיוונים שונים של המסך ולגדלים שונים של גורם הצורה. Compose מציע כמה מנגנונים מוכנים לשימוש שבעזרתם תוכלו להתאים את הפריסות הניתנות ליצירה להגדרות מסך שונות.

אילוצים

כדי לדעת מהם האילוצים שמגיעים מהרכיב ההורה ולתכנן את הפריסה בהתאם, אפשר להשתמש ב-BoxWithConstraints. אילוצים למדידת הביצועים מופיעים בהיקף של פונקציית הלמה של התוכן. אפשר להשתמש באילוצים למדידת מודעות כדי ליצור פריסות שונות בהגדרות מסך שונות:

@Composable
fun WithConstraintsComposable() {
    BoxWithConstraints {
        Text("My minHeight is $minHeight while my maxWidth is $maxWidth")
    }
}

פריסות שמבוססות על משבצות

ב-Compose יש מגוון רחב של רכיבים שאפשר לשלב (composables) שמבוססים על Material Design, עם יחסי התלות androidx.compose.material:material (שכלולים כשיוצרים פרויקט Compose ב-Android Studio) כדי שיהיה קל ליצור ממשקי משתמש. המערכת מספקת רכיבים כמו Drawer,‏ FloatingActionButton ו-TopAppBar.

רכיבי Material משתמשים הרבה בממשקי API של משבצות, דפוס ש-Compose מציג כדי להוסיף שכבת התאמה אישית מעל רכיבים שניתנים לשילוב. הגישה הזו מאפשרת ליצור רכיבים גמישים יותר, כי הם מקבלים רכיב צאצא שיכול להגדיר את עצמו במקום לחשוף כל פרמטר הגדרה של הצאצא. משבצות משאירות מקום ריק בממשק המשתמש, והמפתח יכול למלא אותו כרצונו. לדוגמה, אלה הן המשבצות שאפשר להתאים אישית ב-TopAppBar:

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

בדרך כלל, רכיבים מורכבים מקבלים פונקציית lambda מורכבת מסוג 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 ->
            // ...
        }
    }
}