เฟสของ Jetpack Compose

Compose แสดงผลเฟรมผ่าน ระยะ ที่แตกต่างกันหลายระยะ เช่นเดียวกับชุดเครื่องมือ UI อื่นๆ ส่วนใหญ่ ตัวอย่างเช่น ระบบ Android View มี 3 ระยะหลักๆ ได้แก่ การวัด การจัดวาง และการวาด Compose มีลักษณะคล้ายกันมาก แต่มีระยะเพิ่มเติมที่สำคัญซึ่งเรียกว่า การจัดองค์ประกอบ ในตอนเริ่มต้น

เอกสารประกอบของ Compose อธิบายการจัดองค์ประกอบไว้ในหัวข้อ "แนวคิดในการใช้ Compose" และ "สถานะและ Jetpack Compose"

3 ระยะของเฟรม

Compose มี 3 ระยะหลักๆ ดังนี้

  1. การจัดองค์ประกอบ: UI ที่จะแสดง Compose จะเรียกใช้ฟังก์ชันที่ประกอบกันได้และสร้างคำอธิบาย UI
  2. การจัดวาง: ตำแหน่ง ที่จะวาง UI ระยะนี้ประกอบด้วย 2 ขั้นตอน ได้แก่ การวัดและการจัดวาง องค์ประกอบการจัดวางจะวัดและจัดวางตัวเอง รวมถึงองค์ประกอบย่อยในพิกัด 2 มิติสำหรับแต่ละโหนดในแผนผังการจัดวาง
  3. การวาด: วิธี แสดงผล องค์ประกอบ UI จะวาดลงใน Canvas ซึ่งโดยปกติจะเป็นหน้าจออุปกรณ์
ทั้ง 3 เฟสที่ Compose แปลงข้อมูลเป็น UI (ตามลำดับ ได้แก่ ข้อมูล องค์ประกอบ เลย์เอาต์ การวาดภาพ และ UI)
รูปที่ 1 3 ระยะที่ Compose แปลงข้อมูลเป็น UI

โดยทั่วไปแล้วระยะเหล่านี้จะมีลำดับเหมือนกัน ซึ่งช่วยให้ข้อมูลไหลไปในทิศทางเดียวจากการจัดองค์ประกอบไปยังการจัดวาง ไปจนถึงการวาดเพื่อสร้างเฟรม (หรือที่เรียกว่าโฟลว์ข้อมูลแบบทิศทางเดียว) BoxWithConstraints, LazyColumn, และ LazyRow เป็นข้อยกเว้นที่สำคัญ ซึ่งการจัดองค์ประกอบขององค์ประกอบย่อย จะขึ้นอยู่กับระยะการจัดวางขององค์ประกอบระดับบน

ในเชิงแนวคิด ระยะเหล่านี้จะเกิดขึ้นสำหรับทุกเฟรม แต่เพื่อเพิ่มประสิทธิภาพ Compose จะหลีกเลี่ยงการทำซ้ำงานที่จะคำนวณผลลัพธ์เดียวกันจากอินพุตเดียวกันในระยะทั้งหมดนี้ Compose จะข้ามการเรียกใช้ฟังก์ชันที่ ประกอบกันได้หากสามารถใช้ผลลัพธ์ก่อนหน้าซ้ำได้ และ UI ของ Compose จะไม่ จัดวางหรือวาดแผนผังทั้งหมดใหม่หากไม่จำเป็น Compose จะทำงานเฉพาะจำนวนขั้นต่ำที่จำเป็นในการอัปเดต UI การเพิ่มประสิทธิภาพนี้เป็นไปได้เนื่องจาก Compose ติดตามการอ่านสถานะภายในระยะต่างๆ

ทำความเข้าใจระยะต่างๆ

ส่วนนี้จะอธิบายวิธีดำเนินการ 3 ระยะของ Compose สำหรับฟังก์ชันที่ประกอบกันได้โดยละเอียด

การจัดองค์ประกอบ

ในระยะการจัดองค์ประกอบ รันไทม์ของ Compose จะเรียกใช้ฟังก์ชันที่ประกอบกันได้และแสดงผลโครงสร้างแผนผังที่แสดง UI แผนผัง UI นี้ประกอบด้วยโหนดการจัดวางที่มีข้อมูลทั้งหมดที่จำเป็นสำหรับระยะถัดไป ดังที่แสดงในวิดีโอต่อไปนี้

รูปที่ 2 แผนผังที่แสดง UI ซึ่งสร้างขึ้นในระยะการจัดองค์ประกอบ

ส่วนย่อยของโค้ดและแผนผัง UI จะมีลักษณะดังนี้

ข้อมูลโค้ดที่มี Composable 5 รายการและโครงสร้าง UI ที่ได้ โดยมีโหนดย่อยแยกออกจากโหนดหลัก
รูปที่ 3 ส่วนย่อยของแผนผัง UI พร้อมโค้ดที่เกี่ยวข้อง

ในตัวอย่างเหล่านี้ ฟังก์ชันที่ประกอบกันได้แต่ละฟังก์ชันในโค้ดจะแมปกับโหนดการจัดวางเดียวในแผนผัง UI ในตัวอย่างที่ซับซ้อนมากขึ้น ฟังก์ชันที่ประกอบกันได้อาจมีตรรกะและการควบคุมโฟลว์ และสร้างแผนผังที่แตกต่างกันตามสถานะที่ต่างกัน

การจัดวาง

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

รูปที่ 4 การวัดและการจัดวางโหนดการจัดวางแต่ละโหนดในแผนผัง UI ระหว่างระยะการจัดวาง

ในระยะการจัดวาง ระบบจะสำรวจแผนผังโดยใช้อัลกอริทึม 3 ขั้นตอนต่อไปนี้

  1. วัดองค์ประกอบย่อย: โหนดจะวัดองค์ประกอบย่อยหากมี
  2. กำหนดขนาดของตัวเอง: โหนดจะกำหนดขนาดของตัวเองตามการวัดเหล่านี้
  3. จัดวางองค์ประกอบย่อย: โหนดองค์ประกอบย่อยแต่ละโหนดจะจัดวางเทียบกับตำแหน่งของโหนด

เมื่อสิ้นสุดระยะนี้ โหนดการจัดวางแต่ละโหนดจะมีข้อมูลต่อไปนี้

  • ความกว้าง และความสูง ที่กำหนด
  • พิกัด x, y ที่ควรวาด

โปรดจำแผนผัง UI จากส่วนก่อนหน้า

ข้อมูลโค้ดที่มี Composable 5 รายการและโครงสร้าง UI ที่ได้ โดยมีโหนดลูกแยกออกจากโหนดแม่

สำหรับแผนผังนี้ อัลกอริทึมจะทำงานดังนี้

  1. Row จะวัดองค์ประกอบย่อย Image และ Column
  2. ระบบจะวัด Image เนื่องจากไม่มีองค์ประกอบย่อย จึงกำหนดขนาดของตัวเองและรายงานขนาดกลับไปยัง Row
  3. จากนั้นระบบจะวัด Column โดยจะวัดองค์ประกอบย่อยของตัวเอง (ฟังก์ชันที่ประกอบกันได้ Text 2 รายการ) ก่อน
  4. ระบบจะวัด Text รายการแรก เนื่องจากไม่มีองค์ประกอบย่อย จึงกำหนดขนาดของตัวเองและรายงานขนาดกลับไปยัง Column
    1. ระบบจะวัด Text รายการที่ 2 เนื่องจากไม่มีองค์ประกอบย่อย จึงกำหนดขนาดของตัวเองและรายงานขนาดกลับไปยัง Column
  5. Column จะใช้การวัดองค์ประกอบย่อยเพื่อกำหนดขนาดของตัวเอง โดยจะใช้ความกว้างสูงสุดขององค์ประกอบย่อยและผลรวมของความสูงขององค์ประกอบย่อย
  6. Column จะจัดวางองค์ประกอบย่อยเทียบกับตัวเอง โดยวางองค์ประกอบย่อยไว้ใต้กันในแนวตั้ง
  7. Row จะใช้การวัดองค์ประกอบย่อยเพื่อกำหนดขนาดของตัวเอง โดยจะใช้ความสูงสูงสุดขององค์ประกอบย่อยและผลรวมของความกว้างขององค์ประกอบย่อย จากนั้นจึงจัดวางองค์ประกอบย่อย

โปรดทราบว่าระบบจะเข้าชมแต่ละโหนดเพียงครั้งเดียว รันไทม์ของ Compose จะต้องผ่านแผนผัง UI เพียงครั้งเดียวเพื่อวัดและจัดวางโหนดทั้งหมด ซึ่งช่วยเพิ่มประสิทธิภาพ เมื่อจำนวนโหนดในแผนผังเพิ่มขึ้น เวลาที่ใช้ในการสำรวจแผนผังจะเพิ่มขึ้นแบบเชิงเส้น ในทางตรงกันข้าม หากเข้าชมแต่ละโหนดหลายครั้ง เวลาในการสำรวจจะเพิ่มขึ้นแบบทวีคูณ

การวาด

ในระยะการวาด ระบบจะสำรวจแผนผังอีกครั้งจากบนลงล่าง และแต่ละโหนดจะวาดตัวเองบนหน้าจอตามลำดับ

รูปที่ 5 ระยะการวาดจะวาดพิกเซลบนหน้าจอ

เมื่อใช้ตัวอย่างก่อนหน้า ระบบจะวาดเนื้อหาของแผนผังดังนี้

  1. Row จะวาดเนื้อหาที่อาจมี เช่น สีพื้นหลัง
  2. Image จะวาดตัวเอง
  3. Column จะวาดตัวเอง
  4. Text รายการแรกและรายการที่ 2 จะวาดตัวเองตามลำดับ

รูปที่ 6 แผนผัง UI และการแสดงผลที่วาด

การอ่านสถานะ

เมื่อคุณอ่าน value ของ snapshot state ในระหว่างระยะใดระยะหนึ่งที่ระบุไว้ก่อนหน้านี้ Compose จะติดตามโดยอัตโนมัติว่าระบบกำลังทำอะไรอยู่เมื่ออ่าน value การติดตามนี้ช่วยให้ Compose เรียกใช้ผู้อ่านอีกครั้งเมื่อ value ของสถานะเปลี่ยนแปลง และเป็นพื้นฐานของความสามารถในการสังเกตสถานะใน Compose

โดยทั่วไป คุณจะสร้างสถานะโดยใช้ mutableStateOf() แล้วเข้าถึงสถานะผ่านวิธีใดวิธีหนึ่งต่อไปนี้ เข้าถึงพร็อพเพอร์ตี้ value โดยตรง หรือใช้พร็อพเพอร์ตี้ Delegate ของ Kotlin ดูข้อมูลเพิ่มเติมเกี่ยวกับพร็อพเพอร์ตี้ Delegate ได้ในหัวข้อ "สถานะใน Composable" สำหรับคำแนะนำนี้ "การอ่านสถานะ" หมายถึงวิธีการเข้าถึงที่เทียบเท่ากันทั้ง 2 วิธี

// State read without property delegate.
val paddingState: MutableState<Dp> = remember { mutableStateOf(8.dp) }
Text(
    text = "Hello",
    modifier = Modifier.padding(paddingState.value)
)

// State read with property delegate.
var padding: Dp by remember { mutableStateOf(8.dp) }
Text(
    text = "Hello",
    modifier = Modifier.padding(padding)
)

ฟังก์ชัน "Getter" และ "Setter" จะใช้เพื่อเข้าถึงและอัปเดต value ของสถานะภายใต้ พร็อพเพอร์ตี้ Delegate ฟังก์ชัน Getter และ Setter เหล่านี้จะเรียกใช้ก็ต่อเมื่อคุณอ้างอิงพร็อพเพอร์ตี้เป็นค่าเท่านั้น ไม่ใช่เมื่อสร้างพร็อพเพอร์ตี้ ซึ่งเป็นเหตุผลที่ทำให้ 2 วิธีที่อธิบายไว้ก่อนหน้านี้เทียบเท่ากัน

บล็อกโค้ดแต่ละบล็อกที่เรียกใช้ซ้ำได้เมื่อสถานะที่อ่านเปลี่ยนแปลงคือ ขอบเขตการรีสตาร์ท Compose จะติดตามการเปลี่ยนแปลง value ของสถานะและขอบเขตการรีสตาร์ทในระยะต่างๆ

การอ่านสถานะแบบแบ่งระยะ

ดังที่กล่าวไว้ก่อนหน้านี้ Compose มี 3 ระยะหลักๆ และ Compose จะติดตามสถานะที่อ่านภายในแต่ละระยะ ซึ่งช่วยให้ Compose แจ้งเฉพาะระยะที่ต้องทำงานสำหรับองค์ประกอบแต่ละรายการของ UI ที่ได้รับผลกระทบ

ส่วนต่อไปนี้จะอธิบายแต่ละระยะและสิ่งที่เกิดขึ้นเมื่ออ่านค่าสถานะภายในระยะนั้น

ระยะที่ 1: การจัดองค์ประกอบ

การอ่านสถานะภายในฟังก์ชัน @Composable หรือบล็อก Lambda จะส่งผลต่อการจัดองค์ประกอบและอาจส่งผลต่อระยะถัดไป เมื่อ value ของสถานะเปลี่ยนแปลง ตัวจัดองค์ประกอบใหม่จะกำหนดเวลาให้เรียกใช้ฟังก์ชันที่ประกอบกันได้ทั้งหมดที่อ่าน value ของสถานะนั้นอีกครั้ง โปรดทราบว่ารันไทม์อาจตัดสินใจข้ามฟังก์ชันที่ประกอบกันได้บางฟังก์ชันหรือทั้งหมดหากอินพุตไม่เปลี่ยนแปลง ดูข้อมูลเพิ่มเติมได้ที่หัวข้อ "การข้ามหากอินพุต ไม่เปลี่ยนแปลง"

UI ของ Compose จะเรียกใช้ระยะการจัดวางและการวาด ทั้งนี้ขึ้นอยู่กับผลลัพธ์ของการจัดองค์ประกอบ ระบบอาจข้ามระยะเหล่านี้หากเนื้อหายังคงเหมือนเดิม รวมถึงขนาดและการจัดวางจะไม่เปลี่ยนแปลง

var padding by remember { mutableStateOf(8.dp) }
Text(
    text = "Hello",
    // The `padding` state is read in the composition phase
    // when the modifier is constructed.
    // Changes in `padding` will invoke recomposition.
    modifier = Modifier.padding(padding)
)

ระยะที่ 2: การจัดวาง

ระยะการจัดวางประกอบด้วย 2 ขั้นตอน ได้แก่ การวัด และ การจัดวาง ขั้นตอนการวัดจะเรียกใช้ Lambda การวัดที่ส่งไปยังฟังก์ชันที่ประกอบกันได้ Layout รวมถึงเมธอด MeasureScope.measure ของอินเทอร์เฟซ LayoutModifier และอื่นๆ ขั้นตอนการจัดวางจะเรียกใช้บล็อกการจัดวางของฟังก์ชัน layout รวมถึงบล็อก Lambda ของ Modifier.offset { … } และฟังก์ชันที่คล้ายกัน

การอ่านสถานะระหว่างแต่ละขั้นตอนเหล่านี้จะส่งผลต่อการจัดวางและอาจส่งผลต่อระยะการวาด เมื่อ value ของสถานะเปลี่ยนแปลง UI ของ Compose จะกำหนดเวลาให้เรียกใช้ระยะการจัดวาง นอกจากนี้ ระบบยังเรียกใช้ระยะการวาดหากขนาดหรือตำแหน่งเปลี่ยนแปลง

var offsetX by remember { mutableStateOf(8.dp) }
Text(
    text = "Hello",
    modifier = Modifier.offset {
        // The `offsetX` state is read in the placement step
        // of the layout phase when the offset is calculated.
        // Changes in `offsetX` restart the layout.
        IntOffset(offsetX.roundToPx(), 0)
    }
)

ระยะที่ 3: การวาด

การอ่านสถานะระหว่างโค้ดการวาดจะส่งผลต่อระยะการวาด ตัวอย่างทั่วไป ได้แก่ Canvas(), Modifier.drawBehind และ Modifier.drawWithContent เมื่อ value ของสถานะเปลี่ยนแปลง UI ของ Compose จะเรียกใช้เฉพาะระยะการวาด

var color by remember { mutableStateOf(Color.Red) }
Canvas(modifier = modifier) {
    // The `color` state is read in the drawing phase
    // when the canvas is rendered.
    // Changes in `color` restart the drawing.
    drawRect(color)
}

แผนภาพแสดงว่าการอ่านสถานะในระหว่างเฟสการวาดจะทริกเกอร์ให้เฟสการวาดทำงานอีกครั้งเท่านั้น

เพิ่มประสิทธิภาพการอ่านสถานะ

เนื่องจาก Compose ติดตามการอ่านสถานะแบบเฉพาะที่ คุณจึงลดปริมาณงานที่ดำเนินการได้โดยการอ่านสถานะแต่ละสถานะในระยะที่เหมาะสม

ลองดูตัวอย่างต่อไปนี้ ตัวอย่างนี้มี Image() ซึ่งใช้ตัวปรับแต่งออฟเซ็ตเพื่อออฟเซ็ตตำแหน่งเลย์เอาต์สุดท้าย ซึ่งส่งผลให้เกิดเอฟเฟกต์พารัลแลกซ์เมื่อผู้ใช้เลื่อน

Box {
    val listState = rememberLazyListState()

    Image(
        // ...
        // Non-optimal implementation!
        Modifier.offset(
            with(LocalDensity.current) {
                // State read of firstVisibleItemScrollOffset in composition
                (listState.firstVisibleItemScrollOffset / 2).toDp()
            }
        )
    )

    LazyColumn(state = listState) {
        // ...
    }
}

โค้ดนี้ใช้งานได้ แต่ส่งผลให้ประสิทธิภาพไม่เป็นไปตามที่ต้องการ โค้ดที่เขียนไว้จะอ่าน value ของสถานะ firstVisibleItemScrollOffset และส่งไปยัง ฟังก์ชัน Modifier.offset(offset: Dp) value ของ firstVisibleItemScrollOffset จะเปลี่ยนแปลงเมื่อผู้ใช้เลื่อน ดังที่คุณได้เรียนรู้ไปแล้วว่า Compose จะติดตามการอ่านสถานะเพื่อให้สามารถรีสตาร์ท (เรียกใช้) โค้ดการอ่านอีกครั้ง ซึ่งในตัวอย่างนี้คือเนื้อหาของ Box

นี่เป็นตัวอย่างการอ่านสถานะภายในระยะการจัดองค์ประกอบ ซึ่งไม่จำเป็นต้องเป็นสิ่งที่ไม่ดี และในความเป็นจริงแล้วเป็นพื้นฐานของการจัดองค์ประกอบใหม่ ซึ่งช่วยให้การเปลี่ยนแปลงข้อมูลแสดงผล UI ใหม่ได้

ประเด็นสำคัญ: ตัวอย่างนี้ไม่เป็นไปตามที่ต้องการเนื่องจากเหตุการณ์การเลื่อนแต่ละครั้งจะส่งผลให้มีการประเมิน วัด จัดวาง และวาดเนื้อหาที่ประกอบกันได้ทั้งหมดอีกครั้ง คุณจะทริกเกอร์ระยะ Compose ในการเลื่อนทุกครั้ง แม้ว่าเนื้อหาที่แสดงจะไม่เปลี่ยนแปลง แต่ตำแหน่ง เปลี่ยนแปลง คุณสามารถเพิ่มประสิทธิภาพการอ่านสถานะเพื่อทริกเกอร์ระยะการจัดวางอีกครั้งเท่านั้น

หักลบด้วย Lambda

ตัวแก้ไขออฟเซ็ตมีอีกเวอร์ชันหนึ่งให้ใช้งานได้: Modifier.offset(offset: Density.() -> IntOffset)

เวอร์ชันนี้ใช้พารามิเตอร์ Lambda ซึ่งบล็อก Lambda จะแสดงผลออฟเซ็ตที่ได้ อัปเดตโค้ดเพื่อใช้งานดังนี้

Box {
    val listState = rememberLazyListState()

    Image(
        // ...
        Modifier.offset {
            // State read of firstVisibleItemScrollOffset in Layout
            IntOffset(x = 0, y = listState.firstVisibleItemScrollOffset / 2)
        }
    )

    LazyColumn(state = listState) {
        // ...
    }
}

แล้วทำไมเวอร์ชันนี้จึงมีประสิทธิภาพมากกว่า บล็อก Lambda ที่คุณระบุให้กับตัวปรับแต่งจะเรียกใช้ในระยะเลย์เอาต์ (โดยเฉพาะอย่างยิ่งในขั้นตอนการจัดวางของระยะเลย์เอาต์) ซึ่งหมายความว่าจะไม่มีการอ่านสถานะ firstVisibleItemScrollOffset ในระหว่างการจัดองค์ประกอบอีกต่อไป เนื่องจาก Compose ติดตามเวลาที่อ่านสถานะ การเปลี่ยนแปลงนี้จึงหมายความว่าหาก value ของ firstVisibleItemScrollOffset เปลี่ยนแปลง Compose จะต้องรีสตาร์ทเฉพาะระยะการจัดวางและการวาด

แน่นอนว่าการอ่านสถานะในระยะการจัดองค์ประกอบมักจำเป็นอย่างยิ่ง แม้ว่าจะเป็นเช่นนั้น แต่ก็มีบางกรณีที่คุณสามารถลดจำนวนการจัดองค์ประกอบใหม่ได้โดยการกรองการเปลี่ยนแปลงสถานะ ดูข้อมูลเพิ่มเติมเกี่ยวกับเรื่องนี้ได้ที่ derivedStateOfแปลงออบเจ็กต์สถานะ 1 รายการหรือหลายรายการเป็นสถานะอื่น สถานะ

ลูปการจัดองค์ประกอบใหม่ (การขึ้นต่อกันแบบวนรอบของระยะ)

คำแนะนำนี้ได้กล่าวไว้ก่อนหน้านี้ว่าระบบจะเรียกใช้ระยะของ Compose ตามลำดับเดิมเสมอ และไม่มีวิธีเปลี่ยนกลับขณะอยู่ในเฟรมเดียวกัน อย่างไรก็ตาม การดำเนินการดังกล่าวไม่ได้ห้ามไม่ให้แอปเข้าสู่ลูปการจัดองค์ประกอบในเฟรม ต่างๆ ลองดูตัวอย่างนี้

Box {
    var imageHeightPx by remember { mutableIntStateOf(0) }

    Image(
        painter = painterResource(R.drawable.rectangle),
        contentDescription = "I'm above the text",
        modifier = Modifier
            .fillMaxWidth()
            .onSizeChanged { size ->
                // Don't do this
                imageHeightPx = size.height
            }
    )

    Text(
        text = "I'm below the image",
        modifier = Modifier.padding(
            top = with(LocalDensity.current) { imageHeightPx.toDp() }
        )
    )
}

ตัวอย่างนี้ใช้คอลัมน์แนวตั้ง โดยมีรูปภาพอยู่ด้านบนและข้อความอยู่ด้านล่าง โดยใช้ Modifier.onSizeChanged() เพื่อรับขนาดที่ได้ของรูปภาพ แล้วใช้ Modifier.padding() กับข้อความเพื่อเลื่อนข้อความลง การแปลงจาก Px กลับเป็น Dp อย่างไม่เป็นธรรมชาติบ่งชี้ว่าโค้ดมีปัญหาบางอย่าง

ปัญหาของตัวอย่างนี้คือโค้ดไม่สามารถจัดวาง "สุดท้าย" ภายในเฟรมเดียว โค้ดต้องอาศัยเฟรมหลายเฟรม ซึ่งทำให้เกิดงานที่ไม่จำเป็นและส่งผลให้ UI กระโดดไปมาบนหน้าจอสำหรับผู้ใช้

การจัดองค์ประกอบเฟรมแรก

ในระยะการจัดองค์ประกอบของเฟรมแรก imageHeightPx จะมีค่าเริ่มต้นเป็น 0 ดังนั้น โค้ดจึงระบุ Modifier.padding(top = 0) ให้กับข้อความ ระยะการจัดวางถัดไปจะเรียกใช้การเรียกกลับของตัวแก้ไข onSizeChanged ซึ่งจะอัปเดต imageHeightPx เป็นความสูงจริงของรูปภาพ จากนั้น Compose จะกำหนดเวลาให้จัดองค์ประกอบใหม่สำหรับเฟรมถัดไป อย่างไรก็ตาม ในระยะการวาดปัจจุบัน ข้อความจะแสดงผลโดยมีระยะห่างภายในเป็น 0 เนื่องจากค่า imageHeightPx ที่อัปเดตยังไม่แสดง

การจัดองค์ประกอบเฟรมที่ 2

Compose จะเริ่มเฟรมที่ 2 ซึ่งทริกเกอร์โดยการเปลี่ยนแปลงค่าของ imageHeightPx ในระยะการจัดองค์ประกอบของเฟรมนี้ ระบบจะอ่านสถานะภายในบล็อกเนื้อหา Box ตอนนี้ข้อความจะมีระยะห่างภายในที่ตรงกับความสูงของรูปภาพอย่างถูกต้อง ในระยะการจัดวาง ระบบจะตั้งค่า imageHeightPx อีกครั้ง อย่างไรก็ตาม จะไม่มีการกำหนดเวลาให้จัดองค์ประกอบใหม่เพิ่มเติมเนื่องจากค่าจะยังคงสอดคล้องกัน

แผนภาพแสดงลูปการจัดองค์ประกอบใหม่ที่การเปลี่ยนแปลงขนาดในระยะเลย์เอาต์ทริกเกอร์การจัดองค์ประกอบใหม่ ซึ่งจะทำให้เลย์เอาต์เกิดขึ้นอีกครั้ง

ตัวอย่างนี้อาจดูเหมือนสร้างขึ้น แต่โปรดระวังรูปแบบทั่วไปต่อไปนี้

  • Modifier.onSizeChanged(), onGloballyPositioned() หรือการดำเนินการจัดวางอื่นๆ
  • อัปเดตสถานะบางอย่าง
  • ใช้สถานะนั้นเป็นอินพุตสำหรับตัวแก้ไขการจัดวาง (padding(), height() หรือที่คล้ายกัน)
  • อาจทำซ้ำ

วิธีแก้ไขตัวอย่างก่อนหน้าคือการใช้ Primitive การจัดวางที่เหมาะสม ตัวอย่างก่อนหน้าสามารถใช้ Column() ได้ แต่คุณอาจมีตัวอย่างที่ซับซ้อนมากขึ้นซึ่งต้องใช้สิ่งที่กำหนดเอง ซึ่งจะต้องเขียนการจัดวางที่กำหนดเอง ดูข้อมูลเพิ่มเติมได้ที่คู่มือการจัดวางที่กำหนดเอง

หลักการทั่วไปที่นี่คือการมีแหล่งข้อมูลที่เชื่อถือได้เพียงแหล่งเดียวสำหรับองค์ประกอบ UI หลายรายการที่ควรวัดและจัดวางโดยอิงตามองค์ประกอบอื่นๆ การใช้ Primitive การจัดวางที่เหมาะสมหรือการสร้างการจัดวางที่กำหนดเองหมายความว่าองค์ประกอบระดับบนที่แชร์กันน้อยที่สุดจะทำหน้าที่เป็นแหล่งข้อมูลที่เชื่อถือได้ซึ่งสามารถประสานความสัมพันธ์ระหว่างองค์ประกอบหลายรายการได้ การใช้สถานะแบบไดนามิกจะละเมิดหลักการนี้