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

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

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

ใน Composable คุณจะขอ intrinsicWidth หรือ intrinsicHeight ได้:

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

เช่น หากถาม minIntrinsicHeight ของ Text ที่มีอนันต์ height ระบบจะแสดงผล height ของ Text ราวกับว่าข้อความถูกวาดใน บรรทัดเดียว

การใช้งานภายใน

ลองจินตนาการว่าเราต้องการสร้าง 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
        )
        Divider(
            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
        )
        Divider(
            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 ของ Composable ของ Row จะเป็นค่าสูงสุด minIntrinsicHeight ของบุตรหลาน ขององค์ประกอบ Divider minIntrinsicHeight มีค่าเป็น 0 เนื่องจากไม่ได้ใช้พื้นที่หากไม่มีข้อจำกัด ให้ Text minIntrinsicHeight จะเป็นข้อความที่ระบุ width ดังนั้น ข้อจำกัด height ขององค์ประกอบ Row จะเป็นค่าสูงสุด minIntrinsicHeight ของ Text จากนั้น Divider จะขยายheightเป็น ข้อจำกัด height ที่กำหนดโดย Row

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

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