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

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

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

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

  1. מדידה של צאצאים
  2. לקבוע את הגודל שלו
  3. ממקמים את הצאצאים שלו

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

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

שימוש בתכונת השינוי של הפריסה

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

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

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

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

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

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. בפרמטר lambda 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))
    }
}

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

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

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

בדומה למשתנה המשנה 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
            }
        }
    }
}

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

כך נעשה שימוש ברכיב ה-composable המותאם אישית:

@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!")
    }
}

כמה רכיבי טקסט מוערמים זה מעל זה בעמודה.

כיוון הפריסה

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

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

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

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

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

מידע נוסף

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

סרטונים