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

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

Intrinsics ช่วยให้คุณค้นหาองค์ประกอบย่อยได้ก่อนที่จะวัดจริง

คุณขอ IntrinsicSize.Min หรือ IntrinsicSize.Max ของ Composable ได้โดยทำดังนี้

  • Modifier.width(IntrinsicSize.Min) - คุณต้องใช้ความกว้างขั้นต่ำเท่าใดจึงจะแสดงเนื้อหาได้อย่างถูกต้อง
  • Modifier.width(IntrinsicSize.Max) - คุณต้องการความกว้างสูงสุดเท่าใดเพื่อ แสดงเนื้อหาอย่างถูกต้อง
  • Modifier.height(IntrinsicSize.Min) - คุณต้องการความสูงขั้นต่ำเท่าใด เพื่อแสดงเนื้อหาอย่างถูกต้อง
  • Modifier.height(IntrinsicSize.Max) - คุณต้องการความสูงสูงสุดเท่าใด เพื่อแสดงเนื้อหาอย่างถูกต้อง

เช่น หากคุณขอ minIntrinsicHeight ของ Text ที่มีข้อจํากัดเป็นอนันต์ width ในเลย์เอาต์ที่กําหนดเอง ระบบจะแสดง height ของ Text พร้อมข้อความที่วาดในบรรทัดเดียว

การใช้งาน Intrinsics

คุณสร้าง Composable ที่แสดงข้อความ 2 รายการบนหน้าจอโดยคั่นด้วย เส้นแบ่งได้ดังนี้

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

โดยใช้ Row ที่มี Composable 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
        )
        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 ขยายเต็มหน้าจอ ซึ่งไม่ใช่ลักษณะการทำงานที่ต้องการ

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

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

เมื่อเปิดตัวอย่าง

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

ความสูงของ Row จะกำหนดดังนี้

  • Row ของ minIntrinsicHeight ที่ประกอบได้คือค่าสูงสุด minIntrinsicHeight ขององค์ประกอบย่อย
  • minIntrinsicHeight ขององค์ประกอบ Divider คือ 0 เนื่องจากไม่ได้ใช้พื้นที่หากไม่มีข้อจำกัด
  • Text minIntrinsicHeight คือข้อความสำหรับ width ที่เฉพาะเจาะจง
  • ดังนั้นข้อจำกัด height ขององค์ประกอบ Row จึงกลายเป็นค่าสูงสุด minIntrinsicHeight ของ Text
  • จากนั้น Divider จะขยาย height เป็นการจำกัด height ที่กําหนดโดย Row

Intrinsic ในเลย์เอาต์ที่กำหนดเอง

เมื่อสร้างตัวปรับแต่ง 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.
}