เฟสของ Jetpack Compose

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

เอกสารประกอบของ Compose อธิบายการคอมโพสในการคิดแบบ Compose และ สถานะและ Jetpack Compose

เฟรมมี 3 เฟส

การแต่งเพลงมี 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 จะข้ามการเรียกใช้ฟังก์ชันที่สามารถคอมโพสได้หากนำผลลัพธ์ก่อนหน้ามาใช้ซ้ำได้ และ Compose UI จะไม่ จัดเลย์เอาต์ใหม่หรือวาดใหม่ทั้งทรีหากไม่จำเป็น Compose จะทำงาน เฉพาะจำนวนขั้นต่ำที่จำเป็นในการอัปเดต UI การเพิ่มประสิทธิภาพนี้เป็นไปได้เนื่องจาก Compose ติดตามการอ่านสถานะภายในเฟสต่างๆ

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

ส่วนนี้จะอธิบายรายละเอียดเพิ่มเติมเกี่ยวกับวิธีดำเนินการทั้ง 3 เฟสของ Compose สำหรับ Composable

การเรียบเรียง

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

รูปที่ 2 โครงสร้างแบบต้นไม้ที่แสดง UI ของคุณซึ่งสร้างขึ้นในเฟสการจัดองค์ประกอบ

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

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

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

การจัดวาง

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

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

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

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

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

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

โปรดระลึกถึงแผนผัง UI จากส่วนก่อนหน้า

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

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

  1. Row จะวัดองค์ประกอบย่อย Image และ Column
  2. ระบบจะวัดImage โดยไม่มีองค์ประกอบย่อย จึงกำหนดขนาดของตัวเองและรายงานขนาดกลับไปยัง Row
  3. จากนั้นจะวัด Column โดยจะวัดขนาดขององค์ประกอบย่อย (Composable 2 รายการ) ก่อนText
  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() จากนั้นเข้าถึงสถานะผ่าน วิธีใดวิธีหนึ่งจาก 2 วิธี ได้แก่ การเข้าถึงพร็อพเพอร์ตี้ value โดยตรง หรือใช้ตัวแทนพร็อพเพอร์ตี้ Kotlin อ่านเพิ่มเติมเกี่ยวกับเรื่องนี้ได้ในสถานะใน Composable สำหรับวัตถุประสงค์ของคู่มือนี้ "การอ่านสถานะ" หมายถึงวิธีการเข้าถึงที่เทียบเท่ากันอย่างใดอย่างหนึ่ง

// 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 ของสถานะ ฟังก์ชัน Getter และ Setter เหล่านี้จะเรียกใช้เมื่อคุณอ้างอิงพร็อพเพอร์ตี้เป็นค่าเท่านั้น และจะไม่เรียกใช้เมื่อมีการสร้างพร็อพเพอร์ตี้ ซึ่งเป็นเหตุผลที่ 2 วิธีที่อธิบายไว้ก่อนหน้านี้ จึงเทียบเท่ากัน

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

การอ่านสถานะแบบเป็นระยะ

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

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

ระยะที่ 1: การเรียบเรียง

การอ่านสถานะภายในบล็อกฟังก์ชันหรือบล็อก Lambda จะส่งผลต่อการคอมโพส และอาจส่งผลต่อเฟสต่อๆ ไป@Composable เมื่อ 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 composable 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) ขณะที่ผู้ใช้เลื่อน firstVisibleItemScrollOffsetvalue จะเปลี่ยนไป ดังที่ได้ทราบแล้ว 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 จะติดตามเมื่อมีการอ่านสถานะ การเปลี่ยนแปลงนี้จึงหมายความว่าหาก firstVisibleItemScrollOffset ของ value เปลี่ยนแปลง Compose จะต้องรีสตาร์ทเฉพาะเลย์เอาต์และขั้นตอนการวาด

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

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

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

Box {
    var imageHeightPx by remember { mutableStateOf(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 เป็นความสูงจริงของรูปภาพ จากนั้นจะกำหนดเวลาการจัดองค์ประกอบใหม่สำหรับเฟรมถัดไป อย่างไรก็ตาม ในระหว่าง ระยะการวาดภาพปัจจุบัน ข้อความจะแสดงผลโดยมีระยะขอบเป็น 0 เนื่องจากค่า imageHeightPx ที่อัปเดตแล้วยังไม่แสดง

การจัดวางเฟรมที่ 2

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

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

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

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

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

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