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

ב-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. כאן אתם מטמיעים אותו כדוגמה. כדי לעשות את זה, משתמשים במגדיר layout כדי למקם את הרכיב הניתן להרכבה במסך באופן ידני. כך נראית ההתנהגות שמתקבלת כשמגדירים את Text top padding לערך 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 lambda, מודדים את Text שמיוצג על ידי הפרמטר שאפשר למדוד באמצעות קריאה ל-measurable.measure(constraints).
  2. כדי לציין את הגודל של הקומפוזבל, קוראים למתודה layout(width, height), שמחזירה גם למבדא שמשמש למיקום האלמנטים העטופים. במקרה הזה, זה הגובה בין קו הבסיס האחרון לבין המרווח הפנימי העליון שנוסף.
  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. השדה Modifier הוחל על Text composable והוצג בתצוגה מקדימה.

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

המשנה layout משנה רק את הקומפוזיציה של השיחה. כדי למדוד ולסדר כמה רכיבי Composable, משתמשים ברכיב Layout במקום זאת. קומפוזיציה (composable) זו מאפשרת למדוד ולסדר את רכיבי הילד באופן ידני. כל פריסות הרמה הגבוהה יותר, כמו 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
            }
        }
    }
}

הקומפוזיציות של הצאצא מוגבלות על ידי האילוצים של 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.

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

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

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

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

מידע נוסף

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

סרטונים