Compose มีตัวแก้ไขมากมายสำหรับลักษณะการทำงานทั่วไปที่พร้อมใช้งานทันที แต่คุณก็สร้างตัวแก้ไขที่กำหนดเองได้ด้วย
ตัวแก้ไขมีหลายส่วน ดังนี้
- Modifier factory
- นี่คือฟังก์ชันส่วนขยายใน
Modifierซึ่งมี API ที่เป็นสำนวนสำหรับตัวแก้ไขและช่วยให้สามารถเชื่อมโยงตัวแก้ไขเข้าด้วยกันได้ โรงงานตัวแก้ไขจะสร้างองค์ประกอบตัวแก้ไขที่ Compose ใช้เพื่อแก้ไข UI
- นี่คือฟังก์ชันส่วนขยายใน
- องค์ประกอบตัวปรับแต่ง
- คุณสามารถใช้ลักษณะการทำงานของตัวแก้ไขได้ที่นี่
คุณสามารถใช้ตัวแก้ไขที่กำหนดเองได้หลายวิธี โดยขึ้นอยู่กับฟังก์ชันการทำงานที่ต้องการ บ่อยครั้งที่วิธีที่ง่ายที่สุดในการใช้ตัวแก้ไขที่กำหนดเองคือการใช้โรงงานตัวแก้ไขที่กำหนดเองซึ่งรวมโรงงานตัวแก้ไขอื่นๆ ที่กำหนดไว้แล้วเข้าด้วยกัน หากต้องการลักษณะการทำงานที่กำหนดเองมากขึ้น ให้ติดตั้งใช้งานองค์ประกอบตัวแก้ไขโดยใช้ Modifier.Node API ซึ่งเป็นระดับที่ต่ำกว่าแต่ให้ความยืดหยุ่นมากกว่า
เชื่อมโยงตัวแก้ไขที่มีอยู่เข้าด้วยกัน
คุณมักจะสร้างตัวแก้ไขที่กำหนดเองได้โดยใช้ตัวแก้ไขที่มีอยู่ ตัวอย่างเช่น Modifier.clip() จะใช้ตัวแก้ไข graphicsLayer กลยุทธ์นี้ใช้องค์ประกอบตัวแก้ไขที่มีอยู่ และคุณต้องจัดเตรียมโรงงานตัวแก้ไขที่กำหนดเองของคุณเอง
ก่อนที่จะใช้ตัวแก้ไขที่กำหนดเอง ให้ดูว่าคุณใช้กลยุทธ์เดียวกันได้หรือไม่
fun Modifier.clip(shape: Shape) = graphicsLayer(shape = shape, clip = true)
หรือหากพบว่าคุณใช้ตัวแก้ไขกลุ่มเดียวกันซ้ำๆ บ่อยครั้ง คุณสามารถรวมตัวแก้ไขเหล่านั้นไว้ในตัวแก้ไขของคุณเองได้ ดังนี้
fun Modifier.myBackground(color: Color) = padding(16.dp) .clip(RoundedCornerShape(8.dp)) .background(color)
สร้างตัวแก้ไขที่กำหนดเองโดยใช้ Composable Modifier Factory
นอกจากนี้ คุณยังสร้างตัวแก้ไขที่กำหนดเองโดยใช้ฟังก์ชันที่ประกอบกันได้เพื่อส่งค่าไปยังตัวแก้ไขที่มีอยู่ได้ด้วย ซึ่งเรียกว่า Composable Modifier Factory
การใช้ Composable Modifier Factory เพื่อสร้างตัวแก้ไขยังช่วยให้คุณใช้ API การเขียนระดับสูงขึ้นได้ด้วย เช่น animate*AsState และ API ภาพเคลื่อนไหวที่สนับสนุนสถานะการเขียนอื่นๆ ตัวอย่างเช่น ข้อมูลโค้ดต่อไปนี้แสดงตัวแก้ไขที่ทำให้การเปลี่ยนแปลงอัลฟ่าเคลื่อนไหวเมื่อเปิด/ปิดใช้
@Composable fun Modifier.fade(enable: Boolean): Modifier { val alpha by animateFloatAsState(if (enable) 0.5f else 1.0f) return this then Modifier.graphicsLayer { this.alpha = alpha } }
หากตัวปรับแต่งที่กำหนดเองเป็นเมธอดอำนวยความสะดวกในการระบุค่าเริ่มต้นจาก CompositionLocal วิธีที่ง่ายที่สุดในการติดตั้งใช้งานคือการใช้ Factory ตัวปรับแต่ง Composable ดังนี้
@Composable fun Modifier.fadedBackground(): Modifier { val color = LocalContentColor.current return this then Modifier.background(color.copy(alpha = 0.5f)) }
แนวทางนี้มีข้อควรระวังบางประการ ซึ่งจะอธิบายโดยละเอียดในส่วนต่อไปนี้
ระบบจะแก้ไขค่า CompositionLocal ที่เว็บไซต์การเรียกใช้ของ Modifier Factory
เมื่อสร้างตัวปรับแต่งที่กำหนดเองโดยใช้ Factory ตัวปรับแต่ง Composable ตัวแปรภายใน Composition จะรับค่าจาก Composition Tree ที่สร้างขึ้น ไม่ใช่ที่ใช้ ซึ่งอาจทำให้เกิดผลลัพธ์ที่ไม่คาดคิด ตัวอย่างเช่น ลองพิจารณาตัวอย่างตัวแก้ไข Composition Local ที่กล่าวถึงก่อนหน้านี้ ซึ่งใช้ฟังก์ชันที่ประกอบกันได้ในลักษณะที่แตกต่างออกไปเล็กน้อย ดังนี้
@Composable fun Modifier.myBackground(): Modifier { val color = LocalContentColor.current return this then Modifier.background(color.copy(alpha = 0.5f)) } @Composable fun MyScreen() { CompositionLocalProvider(LocalContentColor provides Color.Green) { // Background modifier created with green background val backgroundModifier = Modifier.myBackground() // LocalContentColor updated to red CompositionLocalProvider(LocalContentColor provides Color.Red) { // Box will have green background, not red as expected. Box(modifier = backgroundModifier) } } }
หากไม่ต้องการให้ตัวแก้ไขทำงานในลักษณะนี้ ให้ใช้ Modifier.Node ที่กำหนดเองแทน เนื่องจากระบบจะแก้ไข CompositionLocal อย่างถูกต้องที่เว็บไซต์การใช้งานและสามารถยกขึ้นได้อย่างปลอดภัย
ระบบจะไม่ข้ามตัวแก้ไขฟังก์ชันที่ประกอบกันได้
ระบบจะไม่ข้ามตัวแก้ไข Composable Factory เนื่องจากไม่สามารถข้ามฟังก์ชันที่ประกอบกันได้ซึ่งมีค่าที่แสดงผล ซึ่งหมายความว่าระบบจะเรียกใช้ฟังก์ชันตัวแก้ไขในการจัดองค์ประกอบใหม่ทุกครั้ง ซึ่งอาจมีค่าใช้จ่ายสูงหากจัดองค์ประกอบใหม่บ่อยครั้ง
ต้องเรียกใช้ตัวแก้ไขฟังก์ชันที่ประกอบกันได้ภายในฟังก์ชันที่ประกอบกันได้
คุณต้องเรียกใช้ตัวแก้ไข Composable Factory จากภายใน Composition เช่นเดียวกับฟังก์ชันที่ประกอบกันได้ทั้งหมด ซึ่งจะจำกัดตำแหน่งที่สามารถยกตัวแก้ไขได้ เนื่องจากไม่สามารถยกตัวแก้ไขออกจาก Composition ได้ ในทางตรงกันข้าม คุณสามารถยกโรงงานตัวแก้ไขที่ประกอบไม่ได้ออกจากฟังก์ชันที่ประกอบกันได้เพื่อให้ใช้ซ้ำได้ง่ายขึ้นและปรับปรุงประสิทธิภาพ
val extractedModifier = Modifier.background(Color.Red) // Hoisted to save allocations @Composable fun Modifier.composableModifier(): Modifier { val color = LocalContentColor.current.copy(alpha = 0.5f) return this then Modifier.background(color) } @Composable fun MyComposable() { val composedModifier = Modifier.composableModifier() // Cannot be extracted any higher }
ใช้ลักษณะการทำงานของตัวแก้ไขที่กำหนดเองโดยใช้ Modifier.Node
Modifier.Node เป็น API ระดับต่ำกว่าสำหรับการสร้างตัวแก้ไขใน Compose ซึ่งเป็น API เดียวกันกับที่ Compose ใช้ติดตั้งใช้งานตัวแก้ไขของตัวเอง และเป็นวิธีที่มีประสิทธิภาพมากที่สุดในการสร้างตัวแก้ไขที่กำหนดเอง
ใช้ตัวแก้ไขที่กำหนดเองโดยใช้ Modifier.Node
การใช้ตัวแก้ไขที่กำหนดเองโดยใช้ Modifier.Node มี 3 ส่วนดังนี้
- การติดตั้งใช้งาน
Modifier.Nodeที่เก็บตรรกะและสถานะของตัวแก้ไข ModifierNodeElementที่สร้างและอัปเดตอินสแตนซ์โหนดตัวแก้ไข- Modifier factory ที่ไม่บังคับ ตามที่อธิบายไว้ก่อนหน้านี้
คลาส ModifierNodeElement ไม่มีสถานะและมีการจัดสรรอินสแตนซ์ใหม่ทุกครั้งที่มีการจัดองค์ประกอบใหม่ ส่วนคลาส Modifier.Node มีสถานะและจะคงอยู่ตลอดการจัดองค์ประกอบใหม่หลายครั้ง และยังนำมาใช้ซ้ำได้ด้วย
ส่วนต่อไปนี้จะอธิบายแต่ละส่วนและแสดงตัวอย่างการสร้างตัวแก้ไขที่กำหนดเองเพื่อวาดวงกลม
Modifier.Node
การติดตั้งใช้งาน Modifier.Node (ในตัวอย่างนี้คือ CircleNode) จะใช้ฟังก์ชันการทำงานของตัวแก้ไขที่กำหนดเอง
// Modifier.Node private class CircleNode(var color: Color) : DrawModifierNode, Modifier.Node() { override fun ContentDrawScope.draw() { drawCircle(color) } }
ในตัวอย่างนี้ ระบบจะวาดวงกลมด้วยสีที่ส่งไปยังฟังก์ชันตัวแก้ไข
โหนดจะใช้ Modifier.Node รวมถึงประเภทโหนดอย่างน้อย 0 ประเภท โหนดมีหลายประเภทตามฟังก์ชันการทำงานที่ตัวปรับแต่งต้องการ ตัวอย่างก่อนหน้านี้ต้องสามารถวาดได้ จึงใช้ DrawModifierNode ซึ่งช่วยให้ลบล้างเมธอดการวาดได้
ประเภทที่ใช้ได้มีดังนี้
โหนด |
การใช้งาน |
ลิงก์ตัวอย่าง |
|
||
|
||
การใช้อินเทอร์เฟซนี้จะช่วยให้ |
||
|
||
|
||
|
||
|
||
|
||
|
||
ซึ่งจะเป็นประโยชน์ในการรวมการใช้งานโหนดหลายรายการไว้ในรายการเดียว |
||
อนุญาตให้คลาส |
ระบบจะทำให้โหนดใช้งานไม่ได้โดยอัตโนมัติเมื่อมีการเรียกใช้การอัปเดตในองค์ประกอบที่เกี่ยวข้อง เนื่องจากตัวอย่างของเราคือ DrawModifierNode ทุกครั้งที่มีการเรียกใช้การอัปเดตในองค์ประกอบ โหนดจะทริกเกอร์การวาดใหม่และสีจะอัปเดตอย่างถูกต้อง คุณเลือกไม่ใช้การทำให้โหนดใช้งานไม่ได้โดยอัตโนมัติได้ตามรายละเอียดในส่วนเลือกไม่ใช้การทำให้โหนดใช้งานไม่ได้โดยอัตโนมัติ
ModifierNodeElement
ModifierNodeElement เป็นคลาสที่เปลี่ยนแปลงไม่ได้ซึ่งเก็บข้อมูลเพื่อสร้างหรืออัปเดตตัวแก้ไขที่กำหนดเอง
// ModifierNodeElement private data class CircleElement(val color: Color) : ModifierNodeElement<CircleNode>() { override fun create() = CircleNode(color) override fun update(node: CircleNode) { node.color = color } }
การติดตั้งใช้งาน ModifierNodeElement ต้องลบล้างเมธอดต่อไปนี้
create: นี่คือฟังก์ชันที่สร้างอินสแตนซ์โหนดตัวแก้ไข ระบบจะเรียกใช้ฟังก์ชันนี้เพื่อสร้างโหนดเมื่อใช้ตัวแก้ไขเป็นครั้งแรก โดยปกติแล้ว การดำเนินการนี้จะประกอบด้วยการสร้างโหนดและกำหนดค่าด้วยพารามิเตอร์ที่ส่งไปยัง Modifier Factoryupdate: ระบบจะเรียกใช้ฟังก์ชันนี้เมื่อใดก็ตามที่มีการระบุตัวแก้ไขนี้ในจุดเดียวกับที่โหนดนี้มีอยู่แล้ว แต่พร็อพเพอร์ตี้มีการเปลี่ยนแปลง ซึ่งกำหนดโดยเมธอดequalsของคลาส ระบบจะส่งโหนดตัวแก้ไขที่สร้างไว้ก่อนหน้านี้เป็นพารามิเตอร์ไปยังการเรียกupdateณ จุดนี้ คุณควรอัปเดตพร็อพเพอร์ตี้ของโหนดให้สอดคล้องกับพารามิเตอร์ที่อัปเดต ความสามารถในการนำโหนดกลับมาใช้ซ้ำในลักษณะนี้เป็นกุญแจสำคัญในการ เพิ่มประสิทธิภาพที่Modifier.Nodeนำมาใช้ ดังนั้นคุณจึงต้อง อัปเดตโหนดที่มีอยู่แทนการสร้างโหนดใหม่ในเมธอดupdateในตัวอย่างวงกลม สีของโหนดจะได้รับการอัปเดต
นอกจากนี้ การติดตั้งใช้งาน ModifierNodeElement ยังต้องติดตั้งใช้งาน equals และ hashCode ด้วย ระบบจะเรียกใช้ update ก็ต่อเมื่อการเปรียบเทียบความเท่ากันกับองค์ประกอบก่อนหน้าแสดงผลเป็นเท็จ
ตัวอย่างก่อนหน้านี้ใช้คลาสข้อมูลเพื่อให้บรรลุเป้าหมายนี้ เมธอดเหล่านี้ใช้เพื่อตรวจสอบว่า
โหนดจำเป็นต้องอัปเดตหรือไม่ หากองค์ประกอบมีพร็อพเพอร์ตี้ที่ไม่ส่งผลต่อความจำเป็นในการอัปเดตโหนด หรือคุณต้องการหลีกเลี่ยงคลาสข้อมูลเนื่องจากเหตุผลด้านความเข้ากันได้ของไบนารี คุณสามารถใช้ equals และ hashCode ด้วยตนเองได้ เช่น องค์ประกอบตัวแก้ไขระยะห่างจากขอบ
Modifier factory
นี่คือ API สาธารณะของตัวแก้ไข การใช้งานส่วนใหญ่จะสร้างองค์ประกอบตัวแก้ไขและเพิ่มลงในเชนตัวแก้ไข ดังนี้
// Modifier factory fun Modifier.circle(color: Color) = this then CircleElement(color)
ตัวอย่างที่เสร็จสมบูรณ์
ทั้ง 3 ส่วนนี้จะรวมกันเพื่อสร้างตัวแก้ไขที่กำหนดเองเพื่อวาดวงกลมโดยใช้ API ของ Modifier.Node ดังนี้
// Modifier factory fun Modifier.circle(color: Color) = this then CircleElement(color) // ModifierNodeElement private data class CircleElement(val color: Color) : ModifierNodeElement<CircleNode>() { override fun create() = CircleNode(color) override fun update(node: CircleNode) { node.color = color } } // Modifier.Node private class CircleNode(var color: Color) : DrawModifierNode, Modifier.Node() { override fun ContentDrawScope.draw() { drawCircle(color) } }
สถานการณ์ทั่วไปที่ใช้ Modifier.Node
เมื่อสร้างตัวแก้ไขที่กำหนดเองด้วย Modifier.Node ต่อไปนี้คือสถานการณ์ที่พบบ่อยซึ่งคุณอาจพบ
ไม่มีพารามิเตอร์
หากตัวแก้ไขไม่มีพารามิเตอร์ ก็ไม่จำเป็นต้องอัปเดต และไม่จำเป็นต้องเป็นคลาสข้อมูลด้วย ต่อไปนี้เป็นตัวอย่างการใช้งานตัวแก้ไขที่ใช้ระยะห่างจากขอบในปริมาณคงที่กับ Composable
fun Modifier.fixedPadding() = this then FixedPaddingElement data object FixedPaddingElement : ModifierNodeElement<FixedPaddingNode>() { override fun create() = FixedPaddingNode() override fun update(node: FixedPaddingNode) {} } class FixedPaddingNode : LayoutModifierNode, Modifier.Node() { private val PADDING = 16.dp override fun MeasureScope.measure( measurable: Measurable, constraints: Constraints ): MeasureResult { val paddingPx = PADDING.roundToPx() val horizontal = paddingPx * 2 val vertical = paddingPx * 2 val placeable = measurable.measure(constraints.offset(-horizontal, -vertical)) val width = constraints.constrainWidth(placeable.width + horizontal) val height = constraints.constrainHeight(placeable.height + vertical) return layout(width, height) { placeable.place(paddingPx, paddingPx) } } }
อ้างอิงองค์ประกอบในพื้นที่
ตัวแก้ไข Modifier.Node จะไม่สังเกตการเปลี่ยนแปลงของออบเจ็กต์สถานะ Compose เช่น CompositionLocal โดยอัตโนมัติ ข้อดีที่ตัวปรับแต่ง Modifier.Node มีเหนือตัวปรับแต่งที่สร้างขึ้นด้วย Composable Factory คือสามารถอ่านค่าของ Composition Local จากตำแหน่งที่ใช้ตัวปรับแต่งในแผนผัง UI ไม่ใช่ตำแหน่งที่มีการจัดสรรตัวปรับแต่ง โดยใช้ currentValueOf
อย่างไรก็ตาม อินสแตนซ์โหนดตัวแก้ไขจะไม่สังเกตการเปลี่ยนแปลงสถานะโดยอัตโนมัติ หากต้องการตอบสนองต่อการเปลี่ยนแปลงของ CompositionLocal โดยอัตโนมัติ คุณสามารถอ่านค่าปัจจุบันภายในขอบเขตได้ ดังนี้
DrawModifierNode:ContentDrawScopeLayoutModifierNode:MeasureScopeและIntrinsicMeasureScopeSemanticsModifierNode:SemanticsPropertyReceiver
ตัวอย่างนี้จะสังเกตค่าของ LocalContentColor เพื่อวาดพื้นหลังตามสีของค่าดังกล่าว เนื่องจาก ContentDrawScope สังเกตการเปลี่ยนแปลงของสแนปชอต ระบบจะวาดใหม่โดยอัตโนมัติเมื่อค่าของ LocalContentColor เปลี่ยนแปลง
class BackgroundColorConsumerNode : Modifier.Node(), DrawModifierNode, CompositionLocalConsumerModifierNode { override fun ContentDrawScope.draw() { val currentColor = currentValueOf(LocalContentColor) drawRect(color = currentColor) drawContent() } }
หากต้องการตอบสนองต่อการเปลี่ยนแปลงสถานะนอกขอบเขตและอัปเดตตัวแก้ไขโดยอัตโนมัติ ให้ใช้ ObserverModifierNode
ตัวอย่างเช่น Modifier.scrollable ใช้เทคนิคนี้เพื่อสังเกตการเปลี่ยนแปลงใน LocalDensity ตัวอย่างแบบง่ายแสดงอยู่ในตัวอย่างต่อไปนี้
class ScrollableNode : Modifier.Node(), ObserverModifierNode, CompositionLocalConsumerModifierNode { // Place holder fling behavior, we'll initialize it when the density is available. val defaultFlingBehavior = DefaultFlingBehavior(splineBasedDecay(UnityDensity)) override fun onAttach() { updateDefaultFlingBehavior() observeReads { currentValueOf(LocalDensity) } // monitor change in Density } override fun onObservedReadsChanged() { // if density changes, update the default fling behavior. updateDefaultFlingBehavior() } private fun updateDefaultFlingBehavior() { val density = currentValueOf(LocalDensity) defaultFlingBehavior.flingDecay = splineBasedDecay(density) } }
สร้างภาพเคลื่อนไหวให้ตัวปรับแต่ง
การติดตั้งใช้งาน Modifier.Node มีสิทธิ์เข้าถึง coroutineScope ซึ่งจะช่วยให้ใช้ Compose Animatable API ได้ ตัวอย่างเช่น ข้อมูลโค้ดนี้จะแก้ไข CircleNode ที่แสดงก่อนหน้านี้ให้ค่อยๆ ปรากฏและค่อยๆ หายไปซ้ำๆ
class CircleNode(var color: Color) : Modifier.Node(), DrawModifierNode { private lateinit var alpha: Animatable<Float, AnimationVector1D> override fun ContentDrawScope.draw() { drawCircle(color = color, alpha = alpha.value) drawContent() } override fun onAttach() { alpha = Animatable(1f) coroutineScope.launch { alpha.animateTo( 0f, infiniteRepeatable(tween(1000), RepeatMode.Reverse) ) { } } } }
แชร์สถานะระหว่างตัวแก้ไขโดยใช้การมอบสิทธิ์
ตัวแก้ไข Modifier.Node สามารถมอบสิทธิ์ให้โหนดอื่นๆ ได้ ซึ่งมีกรณีการใช้งานมากมาย เช่น การแยกการใช้งานทั่วไปในตัวแก้ไขต่างๆ แต่ก็สามารถใช้เพื่อแชร์สถานะทั่วไปในตัวแก้ไขได้ด้วย
ตัวอย่างเช่น การติดตั้งใช้งานพื้นฐานของโหนดตัวแก้ไขที่คลิกได้ซึ่งแชร์ข้อมูลการโต้ตอบ
class ClickableNode : DelegatingNode() { val interactionData = InteractionData() val focusableNode = delegate( FocusableNode(interactionData) ) val indicationNode = delegate( IndicationNode(interactionData) ) }
เลือกไม่ใช้การทำให้โหนดใช้งานไม่ได้โดยอัตโนมัติ
โหนด Modifier.Node จะใช้ไม่ได้โดยอัตโนมัติเมื่อการเรียก ModifierNodeElement ที่เกี่ยวข้องอัปเดต สำหรับตัวแก้ไขที่ซับซ้อน คุณอาจต้องการเลือกไม่ใช้ลักษณะการทำงานนี้เพื่อให้ควบคุมได้ละเอียดยิ่งขึ้นว่าเมื่อใดที่ตัวแก้ไขจะทำให้เฟสไม่ถูกต้อง
ซึ่งจะเป็นประโยชน์อย่างยิ่งหากตัวแก้ไขที่กำหนดเองแก้ไขทั้งเลย์เอาต์และการวาด การเลือกไม่ใช้การทำให้เป็นโมฆะอัตโนมัติช่วยให้คุณทำให้การวาดเป็นโมฆะได้เมื่อพร็อพเพอร์ตี้ที่เกี่ยวข้องกับการวาดเท่านั้น เช่น color มีการเปลี่ยนแปลง ซึ่งจะช่วยหลีกเลี่ยงการทำให้เลย์เอาต์ไม่ถูกต้องและปรับปรุงประสิทธิภาพของตัวแก้ไขได้
ตัวอย่างสมมติของกรณีนี้แสดงในตัวอย่างต่อไปนี้ซึ่งมีตัวแก้ไขที่มี color, size และ onClick lambda เป็นพร็อพเพอร์ตี้ ตัวแก้ไขนี้จะทำให้เฉพาะสิ่งที่จำเป็นใช้งานไม่ได้ โดยข้ามการทำให้ใช้งานไม่ได้ที่ไม่จำเป็น:
class SampleInvalidatingNode( var color: Color, var size: IntSize, var onClick: () -> Unit ) : DelegatingNode(), LayoutModifierNode, DrawModifierNode { override val shouldAutoInvalidate: Boolean get() = false private val clickableNode = delegate( ClickablePointerInputNode(onClick) ) fun update(color: Color, size: IntSize, onClick: () -> Unit) { if (this.color != color) { this.color = color // Only invalidate draw when color changes invalidateDraw() } if (this.size != size) { this.size = size // Only invalidate layout when size changes invalidateMeasurement() } // If only onClick changes, we don't need to invalidate anything clickableNode.update(onClick) } override fun ContentDrawScope.draw() { drawRect(color) } override fun MeasureScope.measure( measurable: Measurable, constraints: Constraints ): MeasureResult { val size = constraints.constrain(size) val placeable = measurable.measure(constraints) return layout(size.width, size.height) { placeable.place(0, 0) } } }