การวัดภายในในเลย์เอาต์ของ 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 จะกำหนดดังนี้

  • minIntrinsicHeight ของ Row Composable คือ 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.
}