خطوط المحاذاة في Jetpack Compose

يتيح لك نموذج تصميم Compose استخدام AlignmentLine لإنشاء خطوط محاذاة مخصّصة يمكن أن تستخدمها التصاميم الرئيسية لمحاذاة العناصر التابعة لها وتحديد مواضعها. على سبيل المثال، يمكن استخدام Row خطوط المحاذاة المخصّصة للعناصر الفرعية لمحاذاتها.

عندما يوفّر تنسيق قيمة لـ AlignmentLine معيّن، يمكن للعناصر الرئيسية في التنسيق قراءة هذه القيمة بعد القياس، وذلك باستخدام عامل التشغيل Placeable.get على مثيل Placeable المقابل. واستنادًا إلى موضع AlignmentLine، يمكن للوالدَين بعد ذلك تحديد موضع الأطفال.

تتضمّن بعض العناصر القابلة للإنشاء في Compose خطوط محاذاة. على سبيل المثال، يعرض العنصر القابل للإنشاء 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 مخصّص، يمكنك توفير خطوط محاذاة مخصّصة كي تتمكّن عناصر Layout الرئيسية الأخرى القابلة للإنشاء من استخدامها لمحاذاة العناصر الفرعية وتحديد موضعها وفقًا لذلك.

يوضّح المثال التالي عنصر BarChart مخصّصًا قابل للإنشاء يعرض خطَّي محاذاة، MaxChartValue وMinChartValue، حتى تتمكّن العناصر الأخرى القابلة للإنشاء من المحاذاة مع الحد الأقصى والأدنى لقيمة البيانات في الرسم البياني. تمت محاذاة عنصرَي النص الحد الأقصى والحد الأدنى إلى وسط خطوط المحاذاة المخصّصة.

عنصر BarChart قابل للإنشاء مع عنصر Text محاذٍ لأعلى قيمة وأدنى قيمة للبيانات.
الشكل 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.

@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)         )     } }