פריסות בהתאמה אישית

ב-Compose, רכיבים בממשק המשתמש מיוצגים על ידי הפונקציות הקומפוזביליות שפולטות חלק מממשק המשתמש כשהן מופעלות, ואז מתווספות לעץ של ממשק המשתמש שמוצג במסך. לכל רכיב בממשק המשתמש יש הורה אחד, ויכולים להיות לו הרבה צאצאים. כל רכיב נמצא גם בתוך רכיב ההורה שלו, שמוגדר כמיקום (x, y) וכגודל, שמוגדר כ-width וכ-height.

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

הפריסה של כל צומת בעץ ממשק המשתמש היא תהליך בן שלושה שלבים. כל צומת חייב:

  1. מודדים את כל הילדים
  2. לקבוע את הגודל שלו בעצמו
  3. מיקום הצאצאים שלו
שלושה שלבים של פריסת צומת: מדידת צאצאים, קביעת גודל, מיקום צאצאים
איור 1. שלושת השלבים של פריסת הצומת הם מדידת הצאצאים, קביעת הגודל ומיקום הצאצאים.

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

שימוש בשינוי הפריסה

אפשר להשתמש במשנה layout כדי לשנות את האופן שבו רכיב נמדד ומוצג. ‫Layout הוא למבדה. הפרמטרים שלו כוללים את הרכיב שאפשר למדוד, שמועבר בתור measurable, ואת האילוצים הנכנסים של הרכיב הניתן לשילוב, שמועברים בתור constraints. כך נראה משתנה של פריסה בהתאמה אישית:

fun Modifier.customLayoutModifier() =
    layout { measurable, constraints ->
        // ...
    }

הצגת Text במסך ושליטה במרחק מהחלק העליון לקו הבסיס של שורת הטקסט הראשונה. זה בדיוק מה שהמשתנה paddingFromBaseline עושה. אתם מטמיעים אותו כאן כדוגמה. כדי לעשות את זה, משתמשים ב-modifier‏ layout כדי למקם את ה-composable במסך באופן ידני. כך נראית התוצאה כשהריפוד העליון של Text מוגדר לערך 24.dp:

ההבדל בין ריפוד רגיל של ממשק המשתמש, שמגדיר את המרווח בין הרכיבים, לבין ריפוד טקסט שמגדיר את המרווח בין קו בסיס אחד לקו הבסיס הבא
איור 2. טקסט עם paddingFromBaseline.

זה הקוד שיוצר את הריווח הזה:

fun Modifier.firstBaselineToTop(
    firstBaselineToTop: Dp
) = layout { measurable, constraints ->
    // Measure the composable
    val placeable = measurable.measure(constraints)

    // Check the composable has a first baseline
    check(placeable[FirstBaseline] != AlignmentLine.Unspecified)
    val firstBaseline = placeable[FirstBaseline]

    // Height of the composable with padding - first baseline
    val placeableY = firstBaselineToTop.roundToPx() - firstBaseline
    val height = placeable.height + placeableY
    layout(placeable.width, height) {
        // Where the composable gets placed
        placeable.placeRelative(0, placeableY)
    }
}

הנה הסבר על מה שקורה בקוד הזה:

  1. בפרמטר הלמבדה measurable, מודדים את Text שמיוצג על ידי הפרמטר שניתן למדידה באמצעות קריאה ל-measurable.measure(constraints).
  2. כדי לציין את הגודל של התוכן הקומפוזבילי, מפעילים את השיטה layout(width, height), שמספקת גם פונקציית lambda שמשמשת להצבת הרכיבים שעטופים. במקרה הזה, זה הגובה בין קו הבסיס האחרון לבין המרווח הפנימי העליון שנוסף.
  3. כדי למקם את הרכיבים העטופים במסך, צריך להפעיל את הפונקציה placeable.place(x, y). אם לא ממקמים את הרכיבים שעטופים, הם לא יהיו גלויים. המיקום y תואם למרווח הפנימי העליון: המיקום של קו הבסיס הראשון של הטקסט.

כדי לוודא שהשינוי פועל כמו שצריך, משתמשים בשינוי הזה ב-Text:

@Preview
@Composable
fun TextWithPaddingToBaselinePreview() {
    MyApplicationTheme {
        Text("Hi there!", Modifier.firstBaselineToTop(32.dp))
    }
}

@Preview
@Composable
fun TextWithNormalPaddingPreview() {
    MyApplicationTheme {
        Text("Hi there!", Modifier.padding(top = 32.dp))
    }
}

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

יצירת פריסות בהתאמה אישית

המשתנה layout משנה רק את ה-composable שקורא לו. כדי למדוד ולסדר כמה רכיבים קומפוזביליים, משתמשים במקום זאת ברכיב הקומפוזבילי Layout. הפונקציה הקומפוזבילית הזו מאפשרת למדוד ולפרוס רכיבי צאצא באופן ידני. כל הפריסות ברמה גבוהה יותר, כמו Column ו-Row, נוצרות באמצעות הרכיב הקומפוזבילי Layout.

בדוגמה הזו אנחנו יוצרים גרסה בסיסית מאוד של Column. רוב הפריסות בהתאמה אישית פועלות לפי התבנית הזו:

@Composable
fun MyBasicColumn(
    modifier: Modifier = Modifier,
    content: @Composable () -> Unit
) {
    Layout(
        modifier = modifier,
        content = content
    ) { measurables, constraints ->
        // measure and position children given constraints logic here
        // ...
    }
}

בדומה ל-modifier layout, measurables היא רשימת הצאצאים שצריך למדוד ו-constraints הם האילוצים מההורה. לפי אותו היגיון, אפשר להטמיע את MyBasicColumn כך:

@Composable
fun MyBasicColumn(
    modifier: Modifier = Modifier,
    content: @Composable () -> Unit
) {
    Layout(
        modifier = modifier,
        content = content
    ) { measurables, constraints ->
        // Don't constrain child views further, measure them with given constraints
        // List of measured children
        val placeables = measurables.map { measurable ->
            // Measure each children
            measurable.measure(constraints)
        }

        // Set the size of the layout as big as it can
        layout(constraints.maxWidth, constraints.maxHeight) {
            // Track the y co-ord we have placed children up to
            var yPosition = 0

            // Place children in the parent layout
            placeables.forEach { placeable ->
                // Position item on the screen
                placeable.placeRelative(x = 0, y = yPosition)

                // Record the y co-ord placed up to
                yPosition += placeable.height
            }
        }
    }
}

הרכיבים הניתנים ליצירה שהם צאצאים מוגבלים על ידי האילוצים של Layout (ללא האילוצים של minHeight), והם ממוקמים על סמך ה-yPosition של הרכיב הניתן ליצירה הקודם.

כך משתמשים בפונקציה הניתנת ליצירה בהתאמה אישית:

@Composable
fun CallingComposable(modifier: Modifier = Modifier) {
    MyBasicColumn(modifier.padding(8.dp)) {
        Text("MyBasicColumn")
        Text("places items")
        Text("vertically.")
        Text("We've done it by hand!")
    }
}

כמה רכיבי טקסט מוערמים אחד מעל השני בעמודה.
איור 4. הטמעה בהתאמה אישית של Column.

כיוון הפריסה

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

אם אתם ממקמים רכיבים קומפוזביליים באופן ידני במסך, ה-LayoutDirection הוא חלק מה-LayoutScope של ה-modifier‏ layout או של הרכיב הקומפוזבילי Layout.

כשמשתמשים ב-layoutDirection, ממקמים רכיבים קומפוזביליים באמצעות place. בניגוד ל-method‏ placeRelative, הערך של place לא משתנה בהתאם לכיוון הפריסה (משמאל לימין לעומת מימין לשמאל).

פריסות בהתאמה אישית בפעולה

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

מידע נוסף

במקורות המידע הבאים אפשר למצוא מידע נוסף על פריסות בהתאמה אישית ב-Compose.

סרטונים