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

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

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

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

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

מטרות הפריסות ב-Compose

ההטמעה של מערכת הפריסה ב-Jetpack פיתוח נייטיב מיועדת להשיג שתי מטרות עיקריות:

היכרות עם פונקציות הניתנות להגדרה

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

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

@Composable
fun ArtistCard() {
    Text("Alfred Sisley")
    Text("3 minutes ago")
}

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

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

‫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")
    }
}

הצגה של שני רכיבים אחד על השני

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

השוואה בין שלושה קומפוזיציות פשוטות של פריסה: column,‏ row ו-box

כדי להגדיר את המיקום של הילדים בתוך 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. הצומת ברמה הבסיסית 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, אפשר להשתמש בשינויים כדי לשפר את הפונקציות שלכם. המשנים חיוניים להתאמה אישית של הפריסה. לדוגמה, כאן אנחנו משלבים כמה משנים כדי להתאים אישית את 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 puts space around an element.
  • הקומפוננטה fillMaxWidth ממלאת את הרוחב המקסימלי שמוקצה לה מהרכיב ההורה שלה.
  • size() מציין את הרוחב והגובה המועדפים של רכיב.

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

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

לרשימות ולרשימות עצלניות, אפשר לעיין במסמכים בנושא יצירת רשימות.

פריסות דינמיות

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

מגבלות

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

@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 של slot, תבנית שפיתוח נייטיב מציג כדי להוסיף שכבת התאמה אישית על גבי רכיבים שניתנים להרכבה. הגישה הזו הופכת את הרכיבים לגמישים יותר, כי הם מקבלים רכיב צאצא שיכול להגדיר את עצמו במקום לחשוף כל פרמטר הגדרה של הצאצא. המשבצות משאירות מקום ריק בממשק המשתמש שהמפתח יכול למלא כרצונו. לדוגמה, אלה המשבצות שאפשר להתאים אישית ב-TopAppBar:

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

פונקציות Composable מקבלות בדרך כלל content lambda של פונקציית Composable‏ ( content: @Composable () -> Unit). ממשקי Slot 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 ->
            // ...
        }
    }
}