การวัดภายในในเลย์เอาต์ของ 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 2 รายการของ 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
        )
        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

องค์ประกอบภายในในเลย์เอาต์ที่กำหนดเอง

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