خطوط تراز در Jetpack Compose

مدل چیدمان Compose به شما امکان می‌دهد از AlignmentLine برای ایجاد خطوط تراز سفارشی استفاده کنید که می‌توانند توسط چیدمان‌های ولی برای تراز کردن و موقعیت‌یابی فرزندانشان استفاده شوند. برای مثال، Row می‌تواند از خطوط تراز سفارشی فرزندانش برای تراز کردن آن‌ها استفاده کند.

وقتی چیدمانی مقداری برای AlignmentLine خاصی ارائه می‌دهد، والدین چیدمان می‌توانند این مقدار را پس‌از اندازه‌گیری بااستفاده از عملگر Placeable.get در نمونه Placeable مربوطه بخوانند. براساس موقعیت AlignmentLine، والدین می‌توانند درباره موقعیت فرزندان تصمیم بگیرند.

برخی‌از ترکیب‌شدنی‌ها در Compose ازقبل با خطوط تراز ارائه می‌شوند. برای مثال، BasicText composable خطوط تراز FirstBaseline و LastBaseline را نمایش می‌دهد.

در مثال زیر، یک LayoutModifier سفارشی به‌نام firstBaselineToTop، FirstBaseline را می‌خواند تا حاشیه داخلی را به 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 قابل‌ترکیب سفارشی را نشان می‌دهد که دو خط تراز MaxChartValue و MinChartValue را نشان می‌دهد تا سایر قابل‌ترکیب‌ها بتوانند با حداکثر و حداقل مقدار داده نمودار تراز شوند. دو عنصر نوشتاری، Max و Min، با مرکز خطوط تراز سفارشی تراز شده‌اند.

BarChart composable with Text aligned to the maximum and
minimum data value.
شکل ۲. 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)
        )
    }
}