การจัดแนวบรรทัดใน Jetpack Compose

โมเดลเลย์เอาต์ Compose ช่วยให้คุณใช้ AlignmentLine เพื่อสร้างเส้นแนวที่กำหนดเองได้ ซึ่งเลย์เอาต์ระดับบนสุดสามารถใช้เพื่อจัดแนวและวางตำแหน่งองค์ประกอบย่อย ได้ เช่น Row สามารถใช้เส้นแนวที่กำหนดเองขององค์ประกอบย่อยเพื่อจัดแนวองค์ประกอบย่อย

เมื่อเลย์เอาต์ระบุค่าสำหรับ AlignmentLine ที่เฉพาะเจาะจง เลย์เอาต์ ระดับบนจะอ่านค่านี้ได้หลังจากวัดโดยใช้ตัวดำเนินการ Placeable.get ในอินสแตนซ์ Placeable ที่เกี่ยวข้อง จากตำแหน่งของAlignmentLine ผู้ปกครองจะ กำหนดตำแหน่งของบุตรหลานได้

Composable บางรายการใน Compose มาพร้อมเส้นแนวแล้ว เช่น Composable 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 Composable ที่กำหนดเองหรือ LayoutModifier ที่กำหนดเอง คุณจะระบุ เส้นแนวที่กำหนดเองเพื่อให้ Composable ระดับบนสุดอื่นๆ ใช้จัดแนว และวางตำแหน่ง Composable ระดับล่างได้ตามต้องการ

ตัวอย่างต่อไปนี้แสดง BarChart ที่กำหนดเองซึ่งแสดงเส้นแนว 2 เส้น ได้แก่ MaxChartValue และ MinChartValue เพื่อให้ อื่นๆ จัดแนวกับค่าข้อมูลสูงสุดและต่ำสุดของแผนภูมิได้ องค์ประกอบข้อความ 2 รายการ ได้แก่ 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 ที่กำหนดเอง ให้ระบุบรรทัดการจัดแนวที่กำหนดเองในเมธอด 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()
                        )
                    ) {}
                }
            }
        }
    }
}

ผู้ปกครองโดยตรงและโดยอ้อมของ Composable นี้สามารถใช้บรรทัดการจัดแนวได้ Composable ต่อไปนี้จะสร้างเลย์เอาต์ที่กำหนดเองซึ่งใช้เป็น พารามิเตอร์ 2 Text ช่องและจุดข้อมูล และจัดแนวข้อความ 2 รายการให้สอดคล้องกับ ค่าข้อมูลสูงสุดและต่ำสุดของแผนภูมิ ตัวอย่างของ Composable นี้คือ สิ่งที่แสดงในรูปที่ 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)
        )
    }
}