การวัดภายในในเลย์เอาต์ของ 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 ที่มี 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) จะบังคับให้องค์ประกอบย่อยมีความสูงเท่ากับความสูงขั้นต่ำโดยธรรมชาติ เนื่องจากเป็นแบบเรียกซ้ำ ระบบจะค้นหา 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
        )
        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 Composable minIntrinsicHeight จะเป็นค่าสูงสุด minIntrinsicHeightขององค์ประกอบย่อย Divider ขององค์ประกอบ minIntrinsicHeight คือ 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.
}