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

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

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

คอมโพสได้บางรายการใน Compose มีเส้นแนวมาให้แล้ว ตัวอย่างเช่น คอมโพสได้ BasicText จะแสดงเส้นแนว FirstBaseline และ LastBaseline

ในตัวอย่างต่อไปนี้ LayoutModifier ที่กำหนดเองชื่อ firstBaselineToTop จะอ่าน FirstBaseline เพื่อเพิ่มระยะห่างจากขอบบนให้กับ Text โดยเริ่มจากเส้นฐานแรก

แสดงความแตกต่างระหว่างการเพิ่มระยะเว้นปกติให้กับองค์ประกอบ
กับการใช้ระยะเว้นกับบรรทัดฐานขององค์ประกอบข้อความ
รูปที่ 1 แสดงความแตกต่างระหว่างการเพิ่มระยะห่างจากขอบบนปกติให้กับองค์ประกอบกับการใช้ระยะห่างจากขอบบนกับเส้นฐานขององค์ประกอบ Text

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

Composable BarChart ที่มีข้อความซึ่งจัดแนวให้ตรงกับค่าข้อมูลสูงสุดและ
ต่ำสุด
รูปที่ 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()
                        )
                    ) {}
                }
            }
        }
    }
}

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