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

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

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

חלק מהקומפוזיציות ב-Compose כבר מגיעות עם קווי יישור. לדוגמה, הרכיב BasicText מאפשר להציג את קווי היישור FirstBaseline ו-LastBaseline.

בדוגמה שלמטה, LayoutModifier מותאם אישית שנקרא firstBaselineToTop קורא את FirstBaseline כדי להוסיף ריווח פנימי ל-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, מיושרים למרכז של קווי היישור המותאמים אישית.

איור 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)
        )
    }
}