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

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

בעזרת Intrinsics אפשר להריץ שאילתות על ילדים לפני שהם נמדדים בפועל.

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

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

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

האלמנטים הפנימיים בפעולה

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

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

איך אפשר לעשות את זה? יכול להיות שיש 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 של התוכן הקומפוזבילי 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.
}