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

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

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

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

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

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

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

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

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

הצגת Text במסך ושליטה במרחק מהחלק העליון לקו הבסיס של שורת הטקסט הראשונה. זה בדיוק מה שהמשתנה paddingFromBaseline עושה. אתם מטמיעים אותו כאן כדוגמה. כדי לעשות את זה, משתמשים ב-modifier‏ layout כדי למקם את הרכיב הניתן לTex במסך באופן ידני. כך נראית התוצאה כשהריפוד העליון של 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. כדי לציין את הגודל של התוכן הקומפוזבילי, צריך להפעיל את ה-method‏ layout(width, height), שמספקת גם פונקציית lambda שמשמשת למיקום הרכיבים שעטופים. במקרה הזה, הגובה הוא הגובה של Text בתוספת placeableY, שהוא firstBaselineToTop פחות קו הבסיס הראשון של Text.
  3. כדי למקם את הרכיבים העטופים במסך, מפעילים את placeable.placeRelative(x, y). אם לא ממקמים את הרכיבים העטופים, הם לא יוצגו. בפריסה מימין לשמאל, placeRelative משקף את המיקום של x. אפשר לקרוא מידע נוסף בקטע כיוון הפריסה. המיקום של y הוא firstBaselineToTop פחות קו הבסיס הראשון של הטקסט, כך שקו הבסיס הראשון נמצא במרחק של firstBaselineToTop מהחלק העליון.

כדי לוודא שהשינוי פועל כצפוי, משתמשים בשינוי הזה ב-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, והם ממוקמים על סמך ה-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 זמין במאפיין layoutDirection של MeasureScope ב-modifier של layout או ברכיב הקומפוזבילי Layout.

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

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

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

מידע נוסף

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

סרטונים