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

אחד הכללים של 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 עם שני Texts בתוכו שמתרחב ככל האפשר, ו-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 composable's minIntrinsicHeight יהיה המקסימום minIntrinsicHeight של רכיבי הצאצא שלו. הערך של Divider element's minIntrinsicHeight הוא 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.
}