คอมโพซมีตัวแก้ไขหลายรายการสำหรับลักษณะการทำงานทั่วไปที่พร้อมใช้งานทันที แต่คุณยังสร้างตัวแก้ไขที่กำหนดเองของคุณเองได้ด้วย
ตัวแก้ไขมีหลายส่วนดังนี้
- โรงงานตัวปรับแต่ง
- นี่เป็นฟังก์ชันส่วนขยายใน
Modifier
ซึ่งให้บริการ API ที่เป็นสำนวนสำหรับตัวแก้ไขของคุณ และช่วยให้คุณต่อเชื่อมตัวแก้ไขเข้าด้วยกันได้อย่างง่ายดาย โรงงานตัวแก้ไขจะสร้างองค์ประกอบตัวแก้ไขที่ Compose ใช้เพื่อแก้ไข UI
- นี่เป็นฟังก์ชันส่วนขยายใน
- องค์ประกอบตัวแก้ไข
- ซึ่งส่วนนี้ให้คุณใช้ลักษณะการทํางานของตัวแก้ไขได้
คุณใช้ตัวแก้ไขที่กําหนดเองได้หลายวิธี ทั้งนี้ขึ้นอยู่กับฟังก์ชันการทำงานที่ต้องการ บ่อยครั้งที่วิธีที่ง่ายที่สุดในการใช้ตัวแก้ไขที่กำหนดเองคือการนําโรงงานตัวแก้ไขที่กําหนดเองมาใช้ ซึ่งจะรวมโรงงานตัวแก้ไขอื่นๆ ที่กําหนดไว้แล้วเข้าด้วยกัน หากต้องการลักษณะการทำงานที่กำหนดเองเพิ่มเติม ให้ใช้องค์ประกอบตัวแก้ไขโดยใช้ Modifier.Node
API ซึ่งเป็น 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"
การใช้คอมโพสิเบิล โมดิไฟเออร์ แฟกทอรีเพื่อสร้างโมดิไฟเออร์ยังช่วยให้ใช้ Compose API ระดับสูงขึ้นได้ เช่น animate*AsState
และ Compose 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
วิธีที่ง่ายที่สุดในการติดตั้งใช้งานคือการใช้คอมโพสิเบิล Modifier Factory ดังนี้
@Composable fun Modifier.fadedBackground(): Modifier { val color = LocalContentColor.current return this then Modifier.background(color.copy(alpha = 0.5f)) }
วิธีการนี้มีข้อควรระวังบางอย่างที่ระบุไว้ด้านล่าง
ระบบจะแก้ไขค่า CompositionLocal
ที่ตำแหน่งการเรียกของโรงงานตัวแก้ไข
เมื่อสร้างตัวแก้ไขที่กําหนดเองโดยใช้โรงงานตัวแก้ไขแบบคอมโพสิเบิล ตัวแปรภายในขององค์ประกอบจะนําค่ามาจากต้นไม้องค์ประกอบที่สร้างขึ้นมา ไม่ใช่ใช้ ซึ่งอาจทำให้เกิดผลลัพธ์ที่ไม่คาดคิด ตัวอย่างเช่น มาดูตัวอย่างการใช้คอมโพสิชันของตัวแปรที่แก้ไขเฉพาะที่จากด้านบน ซึ่งติดตั้งใช้งานแตกต่างกันเล็กน้อยโดยใช้ฟังก์ชันคอมโพสิเบิล
@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
ที่กําหนดเองแทน เนื่องจากระบบจะแก้ไขตัวแปรภายในขององค์ประกอบอย่างถูกต้องในเว็บไซต์ที่ใช้งานและสามารถยกระดับได้อย่างปลอดภัย
ระบบจะไม่ข้ามตัวแก้ไขฟังก์ชันที่ประกอบกันได้
ระบบจะไม่ข้ามตัวแก้ไขของคอมโพสิเบิลแฟกทอรีเนื่องจากข้ามฟังก์ชันคอมโพสิเบิลที่มีค่าผลลัพธ์ไม่ได้ ซึ่งหมายความว่าฟังก์ชันตัวแก้ไขของคุณจะเรียกใช้ทุกครั้งที่มีการคอมโพสิชันใหม่ ซึ่งอาจทําให้สิ้นเปลืองหากมีการคอมโพสิชันใหม่บ่อยครั้ง
ต้องเรียกตัวแก้ไขฟังก์ชันที่ประกอบกันได้ภายในฟังก์ชันที่ประกอบกันได้
เช่นเดียวกับฟังก์ชันคอมโพสิเบิลทั้งหมด คุณต้องเรียกตัวแก้ไขคอมโพสิเบิลแฟกทอรีจากภายในคอมโพสิชัน ซึ่งจะจํากัดตําแหน่งที่ยกตัวปรับเปลี่ยนได้ เนื่องจากยกตัวปรับเปลี่ยนออกจากองค์ประกอบไม่ได้ ในทางกลับกัน คุณสามารถยกเลิกการรวมตัวสร้างตัวแก้ไขที่คอมโพสิเบิลออกจากฟังก์ชันคอมโพสิเบิลเพื่อให้นํากลับมาใช้ใหม่ได้ง่ายขึ้นและปรับปรุงประสิทธิภาพได้ ดังนี้
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
ที่สร้างและอัปเดตอินสแตนซ์โหนดตัวแก้ไข- โรงงานตัวแก้ไขที่ไม่บังคับตามที่อธิบายไว้ข้างต้น
ชั้นเรียน 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
: นี่คือฟังก์ชันที่สร้างอินสแตนซ์โหนดตัวแก้ไข ฟังก์ชันนี้จะเรียกใช้เพื่อสร้างโหนดเมื่อใช้ตัวแก้ไขเป็นครั้งแรก โดยปกติแล้ว การดำเนินการนี้จะหมายถึงการสร้างโหนดและกำหนดค่าด้วยพารามิเตอร์ที่ส่งไปยังโรงงานตัวแก้ไขupdate
: ระบบจะเรียกใช้ฟังก์ชันนี้ทุกครั้งที่มีการจัดเตรียมตัวแก้ไขนี้ในจุดเดียวกันกับที่มีโหนดนี้อยู่แล้ว แต่พร็อพเพอร์ตี้มีการเปลี่ยนแปลง ซึ่งจะกำหนดโดยเมธอดequals
ของคลาส ระบบจะส่งโหนดตัวแก้ไขที่สร้างไว้ก่อนหน้านี้เป็นพารามิเตอร์ไปยังการเรียกupdate
เมื่อถึงจุดนี้ คุณควรอัปเดตพร็อพเพอร์ตี้ของโหนดให้สอดคล้องกับพารามิเตอร์ที่อัปเดต ความสามารถในการนําโหนดมาใช้ซ้ำด้วยวิธีนี้เป็นกุญแจสําคัญในการเพิ่มประสิทธิภาพที่Modifier.Node
มอบให้ ดังนั้นคุณต้องอัปเดตโหนดที่มีอยู่แทนการสร้างโหนดใหม่ในวิธีการupdate
ในตัวอย่างวงกลม ระบบจะอัปเดตสีของโหนด
นอกจากนี้ การติดตั้งใช้งาน ModifierNodeElement
ยังต้องใช้ equals
และ hashCode
ด้วย update
จะได้รับเรียกเฉพาะในกรณีที่การเปรียบเทียบแบบเท่ากับกับองค์ประกอบก่อนหน้าแสดงผลเป็นเท็จ
ตัวอย่างด้านบนใช้คลาสข้อมูลเพื่อดำเนินการนี้ วิธีเหล่านี้ใช้เพื่อตรวจสอบว่าโหนดต้องอัปเดตหรือไม่ หากองค์ประกอบมีพร็อพเพอร์ตี้ที่ไม่ได้มีส่วนเกี่ยวข้องกับการพิจารณาว่าต้องอัปเดตโหนดหรือไม่ หรือคุณต้องการหลีกเลี่ยงคลาสข้อมูลเนื่องจากเหตุผลด้านความเข้ากันได้ของไบนารี คุณสามารถใช้ equals
และ hashCode
ด้วยตนเอง เช่น องค์ประกอบตัวแก้ไขระยะห่าง
โรงงานตัวปรับแต่ง
นี่คืออินเทอร์เฟซ API สาธารณะของตัวแปร การใช้งานส่วนใหญ่จะสร้างองค์ประกอบตัวแก้ไขและเพิ่มลงในเชนตัวแก้ไข
// Modifier factory fun Modifier.circle(color: Color) = this then CircleElement(color)
ตัวอย่างที่สมบูรณ์
3 ส่วนนี้มารวมกันเพื่อสร้างตัวแก้ไขที่กําหนดเองเพื่อวาดวงกลมโดยใช้ Modifier.Node
API
// 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
ต่อไปนี้คือสถานการณ์ที่พบได้ทั่วไปซึ่งคุณอาจพบ
พารามิเตอร์เป็น 0
หากตัวแก้ไขไม่มีพารามิเตอร์ ก็ไม่จำเป็นต้องอัปเดต และไม่จำเป็นต้องเป็นคลาสข้อมูลด้วย ต่อไปนี้คือตัวอย่างการใช้งานตัวแก้ไขที่ใช้ระยะห่างจากขอบคงที่กับคอมโพสิเบิล
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
จะไม่สังเกตการเปลี่ยนแปลงออบเจ็กต์สถานะการคอมโพสิทโดยอัตโนมัติ เช่น CompositionLocal
ข้อได้เปรียบของ Modifier.Node
เหนือกว่าตัวแก้ไขที่เพิ่งสร้างขึ้นด้วยคอมโพสิเบิลแฟกทอรีคือสามารถอ่านค่าของคอมโพสิชันในตำแหน่งที่ใช้ตัวแก้ไขในต้นไม้ UI ไม่ใช่ตำแหน่งที่จัดสรรตัวแก้ไขได้โดยใช้ currentValueOf
อย่างไรก็ตาม อินสแตนซ์โหนดตัวแก้ไขจะไม่สังเกตการเปลี่ยนแปลงสถานะโดยอัตโนมัติ หากต้องการตอบสนองต่อการเปลี่ยนแปลงในองค์ประกอบในเครื่องโดยอัตโนมัติ คุณสามารถอ่านค่าปัจจุบันขององค์ประกอบนั้นภายในขอบเขตได้ ดังนี้
DrawModifierNode
:ContentDrawScope
LayoutModifierNode
:MeasureScope
&IntrinsicMeasureScope
SemanticsModifierNode
: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 APIs ได้ ตัวอย่างเช่น ข้อมูลโค้ดนี้แก้ไข CircleNode
จากด้านบนให้ค่อยๆ ปรากฏขึ้นและจางหายไปซ้ำๆ
class CircleNode(var color: Color) : Modifier.Node(), DrawModifierNode { private val alpha = Animatable(1f) override fun ContentDrawScope.draw() { drawCircle(color = color, alpha = alpha.value) drawContent() } override fun onAttach() { 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
และจะไม่ทำให้เลย์เอาต์โมฆะ
วิธีนี้จะช่วยปรับปรุงประสิทธิภาพของตัวแก้ไขได้
ตัวอย่างสมมติของกรณีนี้แสดงอยู่ด้านล่างพร้อมตัวแก้ไขที่มี Lambda color
,
size
และ onClick
เป็นพร็อพเพอร์ตี้ ตัวแก้ไขนี้จะทําให้เฉพาะรายการที่จําเป็นเป็นโมฆะ และข้ามรายการที่ไม่จําเป็น
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) } } }