Jetpack Compose में अलाइनमेंट लाइनें

Compose लेआउट मॉडल की मदद से, AlignmentLine का इस्तेमाल करके, पसंद के मुताबिक अलाइनमेंट लाइन बनाई जा सकती हैं. पैरंट लेआउट, इन लाइन का इस्तेमाल करके अपने चाइल्ड लेआउट को अलाइन और पोज़िशन कर सकते हैं. उदाहरण के लिए, Row अपने बच्चों को अलाइन करने के लिए, उनके हिसाब से अलाइनमेंट लाइन का इस्तेमाल कर सकता है.

जब कोई लेआउट किसी खास AlignmentLine के लिए वैल्यू उपलब्ध कराता है, तो लेआउट के पैरंट, मेज़र करने के बाद इस वैल्यू को पढ़ सकते हैं. इसके लिए, वे उससे जुड़े Placeable इंस्टेंस पर Placeable.get ऑपरेटर का इस्तेमाल करते हैं. AlignmentLine की पोज़िशन के आधार पर, माता-पिता यह तय कर सकते हैं कि बच्चों को कहां बैठाया जाए.

Compose में कुछ कॉम्पोज़ेबल पहले से ही अलाइनमेंट लाइन के साथ आते हैं. उदाहरण के लिए, BasicText कॉम्पोज़ेबल, FirstBaseline और LastBaseline अलाइनमेंट लाइन दिखाता है.

नीचे दिए गए उदाहरण में, firstBaselineToTop नाम का कस्टम LayoutModifier, Text के पहले बेसलाइन से शुरू करके, उसमें पैडिंग जोड़ने के लिए FirstBaseline को पढ़ता है.

पहली इमेज. किसी एलिमेंट में सामान्य पैडिंग जोड़ने और टेक्स्ट एलिमेंट के बेसलाइन पर पैडिंग लागू करने के बीच का अंतर दिखाता है.

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 को कस्टम अलाइनमेंट लाइनों के बीच में अलाइन किया गया है.

दूसरी इमेज. 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 बनाते समय, 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 स्लॉट और डेटा पॉइंट लेता है. साथ ही, दोनों टेक्स्ट को चार्ट के ज़्यादा से ज़्यादा और कम से कम डेटा वैल्यू के साथ अलाइन करता है. इस कॉम्पोज़ेबल की झलक, दूसरी इमेज में दिखाई गई है.

@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)
        )
    }
}