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

ב-Compose, רכיבי ממשק המשתמש מיוצגים על ידי הפונקציות הניתנות ליצירה שמפיקות קטע של ממשק משתמש כשהן מופעלות, ולאחר מכן מתווספות לעץ של ממשק המשתמש שעבר עיבוד (render) במסך. לכל רכיב בממשק המשתמש יש הורה אחד ויכולים להיות לו הרבה צאצאים. כל רכיב נמצא גם בתוך האב שלו, ומוגדר במיקום (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. כדי למדוד את הערך של Text שמיוצג על ידי הפרמטר המדד, קוראים לפונקציה measurable.measure(constraints) בפרמטר lambda‏ measurable.
  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 משנה רק את ה-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
        // ...
    }
}

בדומה למגביל 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 המותאם אישית:

@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. בניגוד לשיטה placeRelative, הערך של place לא משתנה בהתאם לכיוון של הפריסה (שמאלה לימין לעומת ימינה לשמאל).

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

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

מידע נוסף

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

סרטונים