מדידות מהותיות בפריסות אימייל

אחד הכללים של Compose הוא שצריך למדוד את הרכיבים פעם אחת בלבד. אם מודדים את הרכיבים פעמיים, נוצרת חריגה בזמן הריצה. עם זאת, יש מקרים שבהם צריך לקבל מידע מסוים על הילדים לפני שמודדים אותם.

התכונה Intrinsics מאפשרת לשלוח שאילתות לגבי נתוני ילדים לפני שהם נמדדים בפועל.

אפשר לבקש מ-Gemini את 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 עם הטקסט שמוצג בשורה אחת.

פונקציות פנימיות (intrinsics) בפעולה

אפשר ליצור רכיב שאפשר להוסיף לו רכיבים אחרים, שמציג שני טקסטים במסך עם קו מפריד ביניהם:

שני רכיבי טקסט זה לצד זה, עם קו אנכי שמפריד ביניהם

כדי לעשות את זה, משתמשים ב-Row עם שני רכיבי Text composable שממלאים את המקום הפנוי, וברכיב 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 הוא 0, כי אם לא מציינים אילוצים, האלמנט לא תופס מקום.
  • ה-Text minIntrinsicHeight הוא של הטקסט עבור width ספציפי.
  • לכן, המגבלה height של הרכיב Row הופכת למקסימום minIntrinsicHeight של הרכיבים Text.
  • Divider מרחיב את height לאילוץ height שניתן על ידי Row.

מאפיינים מובנים בפריסות בהתאמה אישית

כשיוצרים משנה מותאם אישית של Layout או layout, המערכת מחשבת באופן אוטומטי את המידות הפנימיות על סמך קירובים. לכן, יכול להיות שהחישובים לא יהיו נכונים לכל הפריסות. ממשקי ה-API האלה מציעים אפשרויות לשינוי ברירות המחדל.

כדי לציין את המדידות הפנימיות של Layout מותאם אישית, צריך לבטל את ברירת המחדל של minIntrinsicWidth, minIntrinsicHeight, maxIntrinsicWidth ו-maxIntrinsicHeight של ממשק MeasurePolicy כשיוצרים אותו.

@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 {
                // Measure and layout here
                // ...
            }

            override fun IntrinsicMeasureScope.minIntrinsicWidth(
                measurables: List<IntrinsicMeasurable>,
                height: Int
            ): Int {
                // Logic here
                // ...
            }

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

כשיוצרים משנה מותאם אישית של layout, מבטלים את השיטות הקשורות בממשק LayoutModifier.

fun Modifier.myCustomModifier(/* ... */) = this then object : LayoutModifier {

    override fun MeasureScope.measure(
        measurable: Measurable,
        constraints: Constraints
    ): MeasureResult {
        // Measure and layout here
        // ...
    }

    override fun IntrinsicMeasureScope.minIntrinsicWidth(
        measurable: IntrinsicMeasurable,
        height: Int
    ): Int {
        // Logic here
        // ...
    }

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