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

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

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

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

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

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

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 שאפשר לשלב עם טקסט שמיושר לערך המקסימלי ולערך המינימלי של הנתונים.

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

/**
 * 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, כי הם משמשים ליישור אנכי של ילדים. מדיניות מיזוג מועברת כפרמטר אם כמה פריסות מספקות ערך לקווי ההתאמה האלה. מכיוון שהקואורדינטות של מערכת הפריסה של Compose והקואורדינטות של Canvas מייצגות את [0, 0], הפינה הימנית העליונה והצירים x ו-y הם חיוביים כלפי מטה, ולכן הערך של MaxChartValue תמיד יהיה קטן מ-MinChartValue. לכן, מדיניות המיזוג היא min לקו הבסיס של הערך המקסימלי של נתוני התרשים, ו-max לקו הבסיס של הערך המינימלי של נתוני התרשים.

כשיוצרים Layout או LayoutModifier מותאמים אישית, צריך לציין קווים מותאמים אישית של התאמה ב-method‏ 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)
        )
    }
}