การวัดภายในในเลย์เอาต์ของ Compose

กฎข้อหนึ่งของ Compose คือคุณควรวัดค่าของบุตรหลานเพียงครั้งเดียวเท่านั้น การวัดค่าบุตรหลาน 2 ครั้งจะทำให้เกิดข้อยกเว้นรันไทม์ อย่างไรก็ตาม บางครั้งคุณก็จำเป็นต้องทราบข้อมูลบางอย่างเกี่ยวกับบุตรหลานก่อนทำการวัด

Intrinsics ช่วยให้คุณค้นหาเด็กๆ ได้ก่อนที่จะมีการวัดผลจริง

คุณสามารถขอ intrinsicWidth หรือ intrinsicHeight ของคอมโพสิเบิลได้โดยทำดังนี้

  • (min|max)IntrinsicWidth: ในกรณีนี้ คุณจะวาดเนื้อหาได้อย่างถูกต้องโดยใช้ความกว้างขั้นต่ำ/สูงสุดเท่าใด
  • (min|max)IntrinsicHeight: เมื่อใช้ความสูงนี้ ความสูงขั้นต่ำ/สูงสุดที่คุณวาดเนื้อหาได้อย่างถูกต้องคือเท่าใด

เช่น หากคุณถาม minIntrinsicHeight ของ Text ที่มี height แบบไม่จำกัด ระบบจะแสดงผล height ของ Text ราวกับว่ามีการวาดข้อความเป็นบรรทัดเดียว

การใช้งาน Intrinsics

สมมติว่าเราต้องการสร้างคอมโพสิเบิลที่แสดงข้อความ 2 รายการบนหน้าจอโดยคั่นด้วยตัวแบ่ง เช่นนี้

องค์ประกอบข้อความ 2 รายการอยู่ข้างกันโดยมีตัวแบ่งแนวตั้งคั่นระหว่างกลาง

เราจะดำเนินการนี้ได้อย่างไร เราอาจมี Row ที่มี Text 2 ตัวอยู่ข้างในซึ่งขยายได้มากที่สุด และมี 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 ขยายเต็มหน้าจอ ซึ่งไม่ใช่สิ่งที่เราต้องการ

องค์ประกอบข้อความ 2 รายการอยู่ข้างกันโดยมีตัวแบ่งคั่นระหว่างกลาง แต่ตัวแบ่งนั้นยืดลงไปด้านล่างของข้อความ

ปัญหานี้เกิดขึ้นเนื่องจาก 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")
        }
    }
}

เมื่อใช้ฟีเจอร์แสดงตัวอย่าง

องค์ประกอบข้อความ 2 รายการอยู่ข้างกันโดยมีตัวแบ่งแนวตั้งคั่นระหว่างกลาง

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.
}