أسطر المحاذاة في Jetpack Compose

يتيح لك نموذج التصميم "إنشاء" استخدام AlignmentLine لإنشاء أسطر محاذاة مخصّصة يمكن استخدامها من خلال التنسيقات الرئيسية لمحاذاة العناصر الثانوية وتحديد موقعها. على سبيل المثال، Row يمكن أن يستخدم أسطر المحاذاة المخصصة للأطفال لمحاذاة هذه الخطوط.

عندما يوفّر التنسيق قيمة لسمة AlignmentLine معيّنة، يمكن للوالدَين تنسيق هذه القيمة بعد قياسها، وذلك باستخدام عامل التشغيل Placeable.get على مثيل Placeable المقابل. وبناءً على موضع AlignmentLine، يمكن للوالدَين تحديد موضع الأطفال.

بعض العناصر القابلة للإنشاء في ميزة "الإنشاء" تأتي مع أسطر محاذاة. على سبيل المثال، تعرض 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، حيث يتم استخدامها لمحاذاة الأطفال عموديًا. يتم تمرير سياسة الدمج كمَعلمة في حال كانت هناك تنسيقات متعددة توفّر قيمة لخطوط المحاذاة هذه. بما أن إحداثيات نظام الإنشاء وإحداثيات 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)
        )
    }
}