Compose লেআউটে ইন্ট্রিনসিক পরিমাপ

Compose-এর একটি নিয়ম হল, আপনাকে চাইল্ড কম্পোজেবল একবারই পরিমাপ করতে হবে; চাইল্ড কম্পোজেবল দুবার পরিমাপ করলে রানটাইম এক্সেপশন থ্রো করে। তবে, এমন কিছু সময় আসে যখন আপনার সন্তানের উচ্চতা পরিমাপ করার আগে তার সম্পর্কে কিছু তথ্য প্রয়োজন হয়।

Intrinsics আপনাকে চিলড্রেনকে কোয়েরি করতে দেয়, তাদের আসলে পরিমাপ করার আগে।

কম্পোজেবলকে আপনি IntrinsicSize.Min বা IntrinsicSize.Max-এর জন্য বলতে পারেন:

  • Modifier.width(IntrinsicSize.Min) - আপনার কন্টেন্ট সঠিকভাবে দেখানোর জন্য ন্যূনতম কত প্রস্থ প্রয়োজন?
  • Modifier.width(IntrinsicSize.Max) - আপনার কন্টেন্ট সঠিকভাবে দেখানোর জন্য সর্বাধিক কত প্রস্থ প্রয়োজন?
  • Modifier.height(IntrinsicSize.Min) - আপনার কন্টেন্ট সঠিকভাবে দেখানোর জন্য ন্যূনতম কত উচ্চতা প্রয়োজন?
  • Modifier.height(IntrinsicSize.Max) - আপনার কন্টেন্ট সঠিকভাবে দেখানোর জন্য সর্বাধিক কত উচ্চতা প্রয়োজন?

যেমন, আপনি যদি কাস্টম লে-আউটে অসীম width সীমাবদ্ধতা সহ Text-এর minIntrinsicHeight-কে জিজ্ঞাসা করেন, তাহলে এটি একটি লাইনে আঁকা টেক্সট সহ Text-এর height ফিরিয়ে দেয়।

ইন্ট্রিনসিক্স ইন অ্যাকশন

আপনি এমন একটি কম্পোজেবল তৈরি করতে পারেন যা স্ক্রিনে দুটি টেক্সট দেখায় এবং সেগুলি একটি ডিভাইডার দিয়ে আলাদা করা থাকে:

দুটি টেক্সট এলিমেন্ট পাশাপাশি, তাদের মধ্যে একটি উল্লম্ব ডিভাইডার সহ

এটি করতে, দুটি Text কম্পোজেবল সহ একটি Row ব্যবহার করুন যা উপলভ্য জায়গা পূরণ করে এবং মাঝখানে একটি 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) তার চাইল্ডের সাইজ এমনভাবে নির্ধারণ করে যাতে সেটি তার ন্যূনতম ইন্ট্রিনসিক উচ্চতার সমান হয়। এই মডিফায়ারটি রিকার্সিভ হওয়ার কারণে, এটি Row-এর minIntrinsicHeight এবং এর চাইল্ডকে কোয়েরি করে।

আপনার কোডে এই মডিফায়ার প্রয়োগ করলে এটি প্রত্যাশা অনুযায়ী কাজ করে:

@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-এর উচ্চতা এইভাবে নির্ধারণ করা হয়:

  • Row কম্পোজেবলের minIntrinsicHeight হল এর চিলড্রেনের সর্বাধিক minIntrinsicHeight।
  • Divider এলিমেন্টের minIntrinsicHeight হল ০, কারণ কোনও সীমাবদ্ধতা না থাকলে এটি জায়গা দখল করে না।
  • Text minIntrinsicHeight হল নির্দিষ্ট width-এর জন্য টেক্সটের।
  • তাই, Row এলিমেন্টের height কনস্ট্রেন্ট Text-এর সর্বাধিক minIntrinsicHeight হয়ে যায়।
  • Divider তারপর Row-এর দেওয়া height সীমাবদ্ধতা অনুযায়ী তার height প্রসারিত করে।

আপনার কাস্টম লেআউটে ইন্ট্রিনসিক্স

কাস্টম Layout বা layout মডিফায়ার তৈরি করার সময়, আনুমানিক হিসেবের উপর ভিত্তি করে ইন্ট্রিনসিক পরিমাপ অটোমেটিক গণনা করা হয়। তাই, সব লেআউটের জন্য গণনা সঠিক নাও হতে পারে। এইসব API, ডিফল্ট সেটিংস ওভাররাইড করার বিকল্প অফার করে।

আপনার কাস্টম Layout-এর ইন্ট্রিনসিক পরিমাপ নির্দিষ্ট করতে, minIntrinsicWidth, minIntrinsicHeight, maxIntrinsicWidth এবং maxIntrinsicHeight ওভাররাইড করুন, যখন MeasurePolicy ইন্টারফেস তৈরি করছেন। আপনার ওভাররাইডকে measure-এর সাথে ম্যাচ করতে হবে। যেমন, একটি কাস্টম কলামে যা তার চাইল্ডকে উল্লম্বভাবে স্ট্যাক করে এবং প্রতিটি চাইল্ডকে সম্পূর্ণ কলামের প্রস্থ দেয়, প্রদত্ত width-এর জন্য ন্যূনতম অন্তর্নিহিত উচ্চতা হল সেই 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.
    }