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

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

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

אפשר לבקש את intrinsicWidth או את intrinsicHeight של רכיב ה-Composable:

  • (min|max)IntrinsicWidth: בהתאם לרוחב הזה, מהו רוחב ה-minimum/maximum שאפשר לצייר בו את התוכן בצורה תקינה?
  • (min|max)IntrinsicHeight: בהתאם לגובה הזה, מהו הגובה המינימלי/מקסימלי שבו אפשר לצייר את התוכן בצורה תקינה?

לדוגמה, אם שואלים את minIntrinsicHeight של Text עם height אינסופי, הפונקציה תחזיר את height של ה-Text כאילו הטקסט צויר בשורה אחת.

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

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

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

איך אפשר לעשות את זה? אפשר להשתמש ב-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
        )
        HorizontalDivider(
            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
        )
        HorizontalDivider(
            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")
        }
    }
}

עם תצוגה מקדימה:

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

הערך של minIntrinsicHeight ב-composable של Row יהיה הערך המקסימלי של minIntrinsicHeight בבניינים שלו. הערך של minIntrinsicHeight ברכיב Divider הוא 0 כי הוא לא תופס מקום אם לא מציינים אילוצים. הערך של minIntrinsicHeight ברכיב Text יהיה הערך של הטקסט בהתאם ל-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.
}