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

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