יישור קווים ב-Jetpack פיתוח נייטיב

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

כשפריסת מקלדת מספקת ערך לAlignmentLine מסוים, רכיבי ההורה של הפריסה יכולים לקרוא את הערך הזה אחרי המדידה, באמצעות האופרטור Placeable.get במופע Placeable המתאים. על סמך המיקום של AlignmentLine, ההורים יכולים להחליט על המיקום של הילדים.

חלק מהרכיבים הניתנים להרכבה ב-Compose כבר מגיעים עם קווי יישור. לדוגמה, רכיב ה-Composable‏ BasicText חושף את קווי היישור FirstBaseline ו-LastBaseline.

בדוגמה הבאה, LayoutModifier מותאם אישית בשם firstBaselineToTop קורא את FirstBaseline כדי להוסיף ריווח ל-Text החל מהבסיס הראשון שלו.

מראה את ההבדל בין הוספת ריווח רגיל לרכיב לבין החלת ריווח על קו הבסיס של רכיב Text.
איור 1. הסרטון מראה את ההבדל בין הוספת ריווח רגיל לרכיב לבין החלת ריווח על קו הבסיס של רכיב Text.

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)
    }
}

@Preview
@Composable
private fun TextWithPaddingToBaseline() {
    MaterialTheme {
        Text("Hi there!", Modifier.firstBaselineToTop(32.dp))
    }
}

כדי לקרוא את FirstBaseline בדוגמה, נעשה שימוש ב-placeable [FirstBaseline] בשלב המדידה.

יצירת קווי יישור מותאמים אישית

כשיוצרים קומפוזיציה מותאמת אישית של Layout או LayoutModifier מותאם אישית, אפשר לספק קווי יישור מותאמים אישית כדי שקומפוזיציות אחרות ברמת ההורה יוכלו להשתמש בהם ליישור ולמיקום של רכיבי הצאצא בהתאם.

בדוגמה הבאה מוצג קומפוזבל מותאם אישית BarChart שחושף שני קווי יישור, MaxChartValue ו-MinChartValue, כדי שקומפוזבלים אחרים יוכלו להתיישר לפי ערך הנתונים המקסימלי והמינימלי בתרשים. שני רכיבי טקסט, Max ו-Min, מיושרים למרכז של קווי היישור המותאמים אישית.

רכיב BarChart שאפשר להרכיב ממנו תרשים עמודות, עם רכיב Text שמוצמד לערך המקסימלי והמינימלי של הנתונים.
איור 2. BarChart composable with Text aligned to the maximum and minimum data value.

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

/**
 * AlignmentLine defined by the maximum data value in a [BarChart]
 */
private val MaxChartValue = HorizontalAlignmentLine(merger = { old, new ->
    min(old, new)
})

/**
 * AlignmentLine defined by the minimum data value in a [BarChart]
 */
private val MinChartValue = HorizontalAlignmentLine(merger = { old, new ->
    max(old, new)
})

קווי היישור המותאמים אישית שמשמשים ליצירת הדוגמה הם מסוג HorizontalAlignmentLine, כי הם משמשים ליישור אנכי של רכיבי צאצא. אם כמה פריסות מספקות ערך לקווי היישור האלה, מדיניות המיזוג מועברת כפרמטר. מכיוון שמערכת הפריסה של כלי הכתיבה מתאמת את הקואורדינטות וקואורדינטות Canvas מייצגות את [0, 0], הפינה הימנית העליונה והצירים x ו-y חיוביים כלפי מטה, הערך של MaxChartValue תמיד יהיה קטן מ-MinChartValue. לכן, מדיניות המיזוג היא min עבור בסיס הערך המקסימלי של נתוני התרשים, ו-max עבור בסיס הערך המינימלי של נתוני התרשים.

כשיוצרים Layout או LayoutModifier מותאמים אישית, צריך לציין קווי יישור מותאמים אישית בשיטה MeasureScope.layout, שמקבלת פרמטר alignmentLines: Map<AlignmentLine, Int>.

@Composable
private fun BarChart(
    dataPoints: List<Int>,
    modifier: Modifier = Modifier,
) {
    val maxValue: Float = remember(dataPoints) { dataPoints.maxOrNull()!! * 1.2f }

    BoxWithConstraints(modifier = modifier) {
        val density = LocalDensity.current
        with(density) {
            // ...
            // Calculate baselines
            val maxYBaseline = // ...
            val minYBaseline = // ...
            Layout(
                content = {},
                modifier = Modifier.drawBehind {
                    // ...
                }
            ) { _, constraints ->
                with(constraints) {
                    layout(
                        width = if (hasBoundedWidth) maxWidth else minWidth,
                        height = if (hasBoundedHeight) maxHeight else minHeight,
                        // Custom AlignmentLines are set here. These are propagated
                        // to direct and indirect parent composables.
                        alignmentLines = mapOf(
                            MinChartValue to minYBaseline.roundToInt(),
                            MaxChartValue to maxYBaseline.roundToInt()
                        )
                    ) {}
                }
            }
        }
    }
}

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

@Composable
private fun BarChartMinMax(
    dataPoints: List<Int>,
    maxText: @Composable () -> Unit,
    minText: @Composable () -> Unit,
    modifier: Modifier = Modifier,
) {
    Layout(
        content = {
            maxText()
            minText()
            // Set a fixed size to make the example easier to follow
            BarChart(dataPoints, Modifier.size(200.dp))
        },
        modifier = modifier
    ) { measurables, constraints ->
        check(measurables.size == 3)
        val placeables = measurables.map {
            it.measure(constraints.copy(minWidth = 0, minHeight = 0))
        }

        val maxTextPlaceable = placeables[0]
        val minTextPlaceable = placeables[1]
        val barChartPlaceable = placeables[2]

        // Obtain the alignment lines from BarChart to position the Text
        val minValueBaseline = barChartPlaceable[MinChartValue]
        val maxValueBaseline = barChartPlaceable[MaxChartValue]
        layout(constraints.maxWidth, constraints.maxHeight) {
            maxTextPlaceable.placeRelative(
                x = 0,
                y = maxValueBaseline - (maxTextPlaceable.height / 2)
            )
            minTextPlaceable.placeRelative(
                x = 0,
                y = minValueBaseline - (minTextPlaceable.height / 2)
            )
            barChartPlaceable.placeRelative(
                x = max(maxTextPlaceable.width, minTextPlaceable.width) + 20,
                y = 0
            )
        }
    }
}
@Preview
@Composable
private fun ChartDataPreview() {
    MaterialTheme {
        BarChartMinMax(
            dataPoints = listOf(4, 24, 15),
            maxText = { Text("Max") },
            minText = { Text("Min") },
            modifier = Modifier.padding(24.dp)
        )
    }
}