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

ตัวอย่างต่อไปนี้แสดง BarChart ที่กำหนดเองซึ่งแสดงเส้นแนว 2 เส้น ได้แก่ MaxChartValue และ MinChartValue เพื่อให้ อื่นๆ จัดแนวกับค่าข้อมูลสูงสุดและต่ำสุดของแผนภูมิได้ องค์ประกอบข้อความ 2 รายการ ได้แก่ สูงสุดและต่ำสุด ได้รับการจัดแนวให้อยู่ตรงกลางของเส้นแนวที่กำหนดเอง

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()
                        )
                    ) {}
                }
            }
        }
    }
}

ผู้ปกครองโดยตรงและโดยอ้อมของ 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)
        )
    }
}