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

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

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

برخی‌از عناصر ترکیبی در Compose ازقبل خطوط تراز دارند. برای مثال، عنصر BasicText قابل ترکیب خطوط تراز 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، را آشکار می‌کند تا عناصر ترکیبی دیگر بتوانند با حداکثر و حداقل مقدار داده‌های نمودار تراز شوند. دو عنصر نوشتاری، بیشینه و کمینه، در مرکز خطوط تراز سفارشی تراز شده‌اند.

عنصر ترکیبی «نمودار میله‌ای» با «نوشتار» ترازشده با حداکثر و
حداقل مقدار داده.
شکل ۲. ‫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 هستند، زیرا از آن‌ها برای تراز کردن عمودی کودکان استفاده می‌شود. خط‌مشی ادغام به‌عنوان پارامتر درصورتی‌که چندین چیدمان مقداری برای این خطوط تراز ارائه دهند، ارسال می‌شود. ازآنجایی‌که سیستم چیدمان «نوشتن» مختصات را هماهنگ می‌کند و مختصات 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)
        )
    }
}