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

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

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

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

در مثال زیر، یک LayoutModifier سفارشی به نام firstBaselineToTop ، FirstBaseline را می‌خواند تا از اولین خط پایه، فاصله‌گذاری (padding) را به 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 composable سفارشی یا یک LayoutModifier سفارشی، می‌توانید خطوط ترازبندی سفارشی ارائه دهید تا سایر composableهای والد بتوانند از آنها برای ترازبندی و قرار دادن فرزندان خود بر اساس آن استفاده کنند.

مثال زیر یک BarChart ترکیبی سفارشی را نشان می‌دهد که دو خط ترازبندی، MaxChartValue و MinChartValue ، را در معرض نمایش قرار می‌دهد، به طوری که سایر نمودارهای ترکیبی می‌توانند با حداکثر و حداقل مقدار داده نمودار تراز شوند. دو عنصر متنی، Max و Min ، در مرکز خطوط ترازبندی سفارشی تراز شده‌اند.

نمودار میله‌ای قابل ترکیب با متن هم‌تراز با حداکثر و حداقل مقدار داده.
شکل ۲. 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 زیر یک طرح‌بندی سفارشی ایجاد می‌کند که به عنوان پارامتر دو اسلات Text و نقاط داده را می‌گیرد و دو متن را با حداکثر و حداقل مقادیر داده‌های نمودار تراز می‌کند. پیش‌نمایش این 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)
        )
    }
}

{% کلمه به کلمه %} {% فعل کمکی %} {% کلمه به کلمه %} {% فعل کمکی %}