เฟสของ Jetpack Compose

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

การเรียบเรียงเพลงจะได้รับการอธิบายในเอกสาร Compose ของเรา ซึ่งรวมถึงการคิดในรูปแบบ Compose และ State และ Jetpack Compose

3 ขั้นตอนของเฟรม

การเขียนมี 3 ระยะหลักๆ ดังนี้

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

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

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

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

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

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

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

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

ส่วนย่อยของโค้ดและโครงสร้าง UI จะมีลักษณะดังต่อไปนี้

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

ในตัวอย่างเหล่านี้ ฟังก์ชัน Composable แต่ละรายการในโค้ดจะแมปกับเลย์เอาต์เดียว ในแผนผัง 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 เป็นลำดับถัดไป วัดจำนวนลูกๆ ของมันเอง (Text สองคน Composables) ก่อน
  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 และการนำเสนอแบบวาด

การอ่านของรัฐ

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

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

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

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

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

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

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

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 { … } เป็นต้น

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

และเพื่อให้แม่นยํายิ่งขึ้น ขั้นตอนการวัดและขั้นตอนตำแหน่งโฆษณาจะแยกจากกัน เริ่มต้นขอบเขตใหม่ ซึ่งหมายความว่าสถานะที่อ่านในขั้นตอนของตำแหน่งโฆษณาจะไม่เรียกใช้ซ้ำ ขั้นตอนการวัดก่อนหน้านั้น อย่างไรก็ตาม 2 ขั้นตอนนี้มัก พันกัน ดังนั้นสถานะที่อ่านในขั้นตอนของตำแหน่งจึงอาจส่งผลต่อการรีสตาร์ทอื่นๆ ที่อยู่ในขั้นตอนการวัด

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 วันและเวลา เมื่อค่าสถานะเปลี่ยนแปลง Compose UI จะเรียกใช้เฉพาะช่วงวาดเท่านั้น

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) {
        // ...
    }
}

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

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

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

มีตัวปรับแต่งออฟเซ็ตอีกเวอร์ชันหนึ่งที่ใช้ ได้แก่ 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 ของเราไม่ใช่ ใช้เวลาเขียนนานขึ้น เพราะเขียนแทร็กเมื่อมีการอ่านสถานะ การเปลี่ยนแปลงนี้หมายความว่าหากค่า firstVisibleItemScrollOffset มีการเปลี่ยนแปลง การเขียนเพียงแค่ต้องรีสตาร์ทเฟสของเลย์เอาต์และขั้นตอนการวาดเท่านั้น

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

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

ลูปการเรียบเรียงใหม่ (การขึ้นต่อกันเฟสแบบวนซ้ำ)

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

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() กับข้อความเพื่อ เลื่อนลง มี Conversion ที่ผิดปกติจาก Px กลับไปเป็น Dp แล้ว แสดงว่าโค้ดมีปัญหาบางอย่าง

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

ลองไปดูแต่ละเฟรมเพื่อดูว่าเกิดอะไรขึ้น

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

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

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

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

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

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

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