اندازه‌گیری‌های ذاتی در چیدمان‌های «نوشتن»

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

Intrinsics به شما امکان می‌دهد قبل‌از اندازه‌گیری واقعی فرزندان، آن‌ها را پرسمان کنید.

برای یک composable، می‌توانید IntrinsicSize.Min یا IntrinsicSize.Max آن را درخواست کنید:

  • Modifier.width(IntrinsicSize.Min) - حداقل عرضی که برای نمایش درست محتوایتان نیاز دارید چقدر است؟
  • Modifier.width(IntrinsicSize.Max) - حداکثر عرضی که برای نمایش درست محتوایتان نیاز دارید چقدر است؟
  • Modifier.height(IntrinsicSize.Min) - حداقل ارتفاعی که برای نمایش درست محتوایتان نیاز دارید چقدر است؟
  • Modifier.height(IntrinsicSize.Max) - حداکثر ارتفاعی که برای نمایش درست محتوایتان نیاز دارید چقدر است؟

برای مثال، اگر minIntrinsicHeight Text را با محدودیت‌های بی‌نهایت width در چیدمان سفارشی درخواست کنید، height Text را با نوشتار ترسیم‌شده در یک خط برمی‌گرداند.

ویژگی‌های ذاتی در عمل

می‌توانید یک composable ایجاد کنید که دو متن را روی صفحه نمایش دهد که با یک جداکننده از هم جدا شده‌اند:

دو عنصر نوشتاری در کنار هم، با یک جداکننده عمودی بین آن‌ها

برای انجام این کار، از Row با دو ترکیب‌پذیر Text که فضای دردسترس را پر می‌کنند و یک Divider در وسط استفاده کنید. Divider باید به بلندی بلندترین Text باشد و باید باریک (width = 1.dp) باشد.

@Composable
fun TwoTexts(modifier: Modifier = Modifier, text1: String, text2: String) {
    Row(modifier = modifier) {
        Text(
            modifier = Modifier
                .weight(1f)
                .padding(start = 4.dp)
                .wrapContentWidth(Alignment.Start),
            text = text1
        )
        VerticalDivider(
            color = Color.Black,
            modifier = Modifier.fillMaxHeight().width(1.dp)
        )
        Text(
            modifier = Modifier
                .weight(1f)
                .padding(end = 4.dp)
                .wrapContentWidth(Alignment.End),

            text = text2
        )
    }
}

Divider به کل صفحه گسترش می‌یابد، که رفتار مطلوب نیست:

دو عنصر نوشتاری در کنار هم، با یک جداکننده بین آن‌ها، اما جداکننده تا زیر پایین نوشتار کشیده شده است

این اتفاق می‌افتد زیرا Row هر فرزند را به‌طور جداگانه اندازه‌گیری می‌کند و از ارتفاع Text نمی‌توان برای محدود کردن Divider استفاده کرد.

برای اینکه Divider فضای دردسترس را با ارتفاع مشخصی پر کند، از اصلاح‌گر height(IntrinsicSize.Min) استفاده کنید.

height(IntrinsicSize.Min) اندازه فرزندانش را به‌اندازه حداقل ارتفاع ذاتی‌شان تعیین می‌کند. چون این اصلاح‌کننده بازگشتی است، minIntrinsicHeight مربوط به Row و فرزندان آن را پُرسمان می‌کند.

اعمال این اصلاح‌کننده به کدتان باعث می‌شود طبق انتظار کار کند:

@Composable
fun TwoTexts(modifier: Modifier = Modifier, text1: String, text2: String) {
    Row(modifier = modifier.height(IntrinsicSize.Min)) {
        Text(
            modifier = Modifier
                .weight(1f)
                .padding(start = 4.dp)
                .wrapContentWidth(Alignment.Start),
            text = text1
        )
        VerticalDivider(
            color = Color.Black,
            modifier = Modifier.fillMaxHeight().width(1.dp)
        )
        Text(
            modifier = Modifier
                .weight(1f)
                .padding(end = 4.dp)
                .wrapContentWidth(Alignment.End),

            text = text2
        )
    }
}

// @Preview
@Composable
fun TwoTextsPreview() {
    MaterialTheme {
        Surface {
            TwoTexts(text1 = "Hi", text2 = "there")
        }
    }
}

با پیش‌نمایش:

دو عنصر نوشتاری در کنار هم، با یک جداکننده عمودی بین آن‌ها

ارتفاع Row به‌صورت زیر تعیین می‌شود:

  • minIntrinsicHeight ترکیب‌پذیر Row حداکثر minIntrinsicHeight فرزندانش است.
  • minIntrinsicHeight عنصر Divider برابر با ۰ است، زیرا اگر محدودیتی داده نشود، فضایی را اشغال نمی‌کند.
  • Text minIntrinsicHeight مربوط به نوشتار برای width خاصی است.
  • بنابراین، محدودیت height عنصر Row به حداکثر minIntrinsicHeight Text تبدیل می‌شود.
  • سپس Divider height خود را به محدودیت height که توسط Row ارائه شده است گسترش می‌دهد.

ویژگی‌های ذاتی در چیدمان‌های سفارشی شما

هنگام ایجاد اصلاح‌کننده سفارشی Layout یا layout، اندازه‌گیری‌های ذاتی به‌طور خودکار براساس تقریب‌ها محاسبه می‌شوند. بنابراین، ممکن است محاسبات برای همه چیدمان‌ها درست نباشد. این میاناهای برنامه‌سازی کاربردی گزینه‌هایی برای لغو این پیش‌فرض‌ها ارائه می‌دهند.

برای مشخص کردن اندازه‌گیری‌های ذاتی Layout سفارشی‌تان، minIntrinsicWidth، minIntrinsicHeight، maxIntrinsicWidth، و maxIntrinsicHeight میانای MeasurePolicy را هنگام ایجاد آن لغو کنید. لغو شما باید با آنچه measure انجام می‌دهد مطابقت داشته باشد. For example, in a custom column that stacks its children vertically and gives each child the full column width, the minimum intrinsic height for a given width is the sum of its children's minimum intrinsic heights for that width:

@Composable
fun MyCustomComposable(
    modifier: Modifier = Modifier,
    content: @Composable () -> Unit
) {
    Layout(
        content = content,
        modifier = modifier,
        measurePolicy = object : MeasurePolicy {
            override fun MeasureScope.measure(
                measurables: List<Measurable>,
                constraints: Constraints
            ): MeasureResult {
                val childConstraints = constraints.copy(minHeight = 0)
                val placeables = measurables.map { measurable ->
                    measurable.measure(childConstraints)
                }
                val width = constraints.constrainWidth(
                    placeables.maxOfOrNull { it.width } ?: 0
                )
                val height = constraints.constrainHeight(
                    placeables.sumOf { it.height }
                )
                return layout(width, height) {
                    var yPosition = 0
                    placeables.forEach { placeable ->
                        placeable.placeRelative(x = 0, y = yPosition)
                        yPosition += placeable.height
                    }
                }
            }

            override fun IntrinsicMeasureScope.minIntrinsicHeight(
                measurables: List<IntrinsicMeasurable>,
                width: Int
            ): Int {
                return measurables.sumOf { measurable ->
                    measurable.minIntrinsicHeight(width)
                }
            }

            // Other intrinsics related methods have a default value,
            // you can override only the methods that you need.
        }
    )
}

هنگام ایجاد اصلاح‌کننده سفارشی layout، روش‌های مرتبط را در میانای LayoutModifier لغو کنید. محاسبه ذاتی باید با measure مطابقت داشته باشد. برای مثال، برای یک height مشخص، اصلاح‌کننده‌ای که حاشیه داخلی ثابتی را به همه طرف‌ها اضافه می‌کند، حاشیه داخلی عمودی را قبل‌از پرسمان minIntrinsicWidth فرزند کم می‌کند و حاشیه داخلی افقی را به نتیجه اضافه می‌کند:

fun Modifier.myCustomModifier(padding: Dp = 8.dp) =
    this then object : LayoutModifier {
        override fun MeasureScope.measure(
            measurable: Measurable,
            constraints: Constraints
        ): MeasureResult {
            val paddingPx = padding.roundToPx()
            val horizontal = paddingPx * 2
            val vertical = paddingPx * 2
            val placeable = measurable.measure(
                constraints.offset(-horizontal, -vertical)
            )
            val width = constraints.constrainWidth(placeable.width + horizontal)
            val height = constraints.constrainHeight(placeable.height + vertical)
            return layout(width, height) {
                placeable.place(paddingPx, paddingPx)
            }
        }

        override fun IntrinsicMeasureScope.minIntrinsicWidth(
            measurable: IntrinsicMeasurable,
            height: Int
        ): Int {
            val paddingPx = padding.roundToPx()
            val horizontal = paddingPx * 2
            val vertical = paddingPx * 2
            val childHeight = if (height == Constraints.Infinity) {
                Constraints.Infinity
            } else {
                (height - vertical).coerceAtLeast(0)
            }
            return measurable.minIntrinsicWidth(childHeight) + horizontal
        }

        // Other intrinsics related methods have a default value,
        // you can override only the methods that you need.
    }