خطوط المحاذاة في 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 مكوّنًا مخصّصًا يعرض خطَّي ALIGNED، MaxChartValue وMinChartValue، لكي تتمكّن العناصر المكوّنة الأخرى من ALIGNED مع الحد الأقصى والحد الأدنى لقيمة البيانات في الرسم البياني. تمّت محاذاة عنصرَي النص الحدّ الأقصى والحدّ الأدنى في منتصف خطوط المحاذاة المخصّصة.

الشكل 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()
                        )
                    ) {}
                }
            }
        }
    }
}

يمكن للعناصر المباشرة وغير المباشرة التي تتضمّن هذا العنصر القابل للتجميع استخدام خطوط alignment. ينشئ العنصر القابل للتجميع التالي تنسيقًا مخصّصًا يستخدِم كهيئَين مَعلمتَين 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)
        )
    }
}