สร้างตัวแก้ไขที่กำหนดเอง

คอมโพซมีตัวแก้ไขหลายรายการสำหรับลักษณะการทำงานทั่วไปที่พร้อมใช้งานทันที แต่คุณยังสร้างตัวแก้ไขที่กำหนดเองของคุณเองได้ด้วย

ตัวแก้ไขมีหลายส่วน ดังนี้

  • โรงงานตัวปรับแต่ง
    • นี่เป็นฟังก์ชันส่วนขยายใน 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 และประเภทโหนดอย่างน้อย 1 ประเภท โหนดมีหลากหลายประเภทโดยขึ้นอยู่กับฟังก์ชันการทำงานของตัวแก้ไข ตัวอย่างข้างต้นต้องวาดได้ จึงใช้ DrawModifierNode ซึ่งทำให้ลบล้างเมธอดวาดได้

ประเภทที่ใช้ได้มีดังนี้

โหนด

การใช้งาน

ลิงก์ตัวอย่าง

LayoutModifierNode

Modifier.Node ที่เปลี่ยนวิธีวัดและวางเลย์เอาต์ของเนื้อหาที่ตัดขึ้นบรรทัดใหม่

ตัวอย่าง

DrawModifierNode

Modifier.Node ที่วาดในพื้นที่ของเลย์เอาต์

ตัวอย่าง

CompositionLocalConsumerModifierNode

การใช้อินเทอร์เฟซนี้จะช่วยให้ Modifier.Node อ่านองค์ประกอบในท้องถิ่นได้

ตัวอย่าง

SemanticsModifierNode

Modifier.Node ที่เพิ่มคีย์/ค่าตามความหมายเพื่อใช้ในการทดสอบ การช่วยเหลือพิเศษ และกรณีการใช้งานที่คล้ายกัน

ตัวอย่าง

PointerInputModifierNode

Modifier.Node ที่ได้รับ PointerInputChanges

ตัวอย่าง

ParentDataModifierNode

Modifier.Node ที่ให้ข้อมูลแก่เลย์เอาต์หลัก

ตัวอย่าง

LayoutAwareModifierNode

Modifier.Node ที่ได้รับการติดต่อกลับ onMeasured และ onPlaced

ตัวอย่าง

GlobalPositionAwareModifierNode

Modifier.Node ที่ได้รับ onGloballyPositioned callback พร้อม LayoutCoordinates สุดท้ายของเลย์เอาต์เมื่อตำแหน่งส่วนกลางของเนื้อหาอาจเปลี่ยนแปลง

ตัวอย่าง

ObserverModifierNode

Modifier.Node ที่ใช้ ObserverNode สามารถระบุการใช้งาน onObservedReadsChanged ของตนเองที่จะเรียกใช้เพื่อตอบสนองต่อการเปลี่ยนแปลงของออบเจ็กต์สแนปชอตที่อ่านภายในบล็อก observeReads

ตัวอย่าง

DelegatingNode

Modifier.Node ที่มอบหมายงานไปยังอินสแตนซ์ Modifier.Node อื่นๆ ได้

ซึ่งจะเป็นประโยชน์ในการคอมโพสิชันการใช้งานโหนดหลายรายการเข้าด้วยกัน

ตัวอย่าง

TraversableNode

อนุญาตให้คลาส Modifier.Node เลื่อนขึ้น/ลงในลําดับชั้นโหนดเพื่อค้นหาคลาสประเภทเดียวกันหรือคีย์ที่ต้องการ

ตัวอย่าง

ระบบจะลบล้างโหนดโดยอัตโนมัติเมื่อเรียกใช้การอัปเดตในองค์ประกอบที่เกี่ยวข้อง เนื่องจากตัวอย่างของเราคือ 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 ต้องลบล้างวิธีการต่อไปนี้

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

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

ตัวอย่างนี้จะตรวจสอบค่าของ 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)
        }
    }
}