ย้ายข้อมูลไปยัง API ของตัวบ่งชี้และ Ripple

เพื่อปรับปรุงประสิทธิภาพองค์ประกอบของคอมโพเนนต์แบบอินเทอร์แอกทีฟที่ใช้ Modifier.clickable เราได้เปิดตัว API ใหม่ API เหล่านี้ทำให้ การติดตั้งใช้งาน Indication เช่น Ripples ที่มีประสิทธิภาพ

androidx.compose.foundation:foundation:1.7.0+ และ androidx.compose.material:material-ripple:1.7.0+ มี API ต่อไปนี้ การเปลี่ยนแปลง:

เลิกใช้งานแล้ว

การแทนที่

Indication#rememberUpdatedInstance

IndicationNodeFactory

rememberRipple()

มี API ใหม่ของ ripple() ในไลบรารี Material แทน

หมายเหตุ: ในบริบทนี้ "ไลบรารีสื่อการเรียนการสอน" หมายถึง androidx.compose.material:material, androidx.compose.material3:material3, androidx.wear.compose:compose-material และ androidx.wear.compose:compose-material3.

RippleTheme

ดังนี้

  • ใช้ API ของไลบรารี Material หรือ RippleConfiguration
  • สร้างการใช้งานระลอกคลื่นระบบการออกแบบของคุณเอง

หน้านี้อธิบายผลกระทบจากการเปลี่ยนแปลงลักษณะการทำงานและคำแนะนำในการย้ายข้อมูลไปยัง API ใหม่

การเปลี่ยนแปลงลักษณะการทำงาน

เวอร์ชันไลบรารีต่อไปนี้มีการเปลี่ยนแปลงลักษณะการทำงานแบบระลอกคลื่น:

  • androidx.compose.material:material:1.7.0+
  • androidx.compose.material3:material3:1.3.0+
  • androidx.wear.compose:compose-material:1.4.0+

ไลบรารี Material เวอร์ชันเหล่านี้ไม่ได้ใช้ rememberRipple() แล้ว แทน พวกเขาใช้ Ripple API ใหม่ ดังนั้นจึงไม่ค้นหา LocalRippleTheme ดังนั้นหากคุณตั้งค่า LocalRippleTheme ในแอปพลิเคชัน Material คอมโพเนนต์จะไม่ใช้ค่าเหล่านี้

หัวข้อต่อไปนี้จะอธิบายถึงวิธีเปลี่ยนกลับไปใช้ลักษณะการทำงานเดิมชั่วคราว โดยไม่ต้องย้ายข้อมูล แต่เราขอแนะนำให้ย้ายข้อมูลไปยัง API ใหม่ สำหรับ ดูวิธีการย้ายข้อมูลที่ย้ายข้อมูลจาก rememberRipple ไปยัง ripple และหัวข้อต่อๆ ไป

อัปเกรดเวอร์ชันไลบรารี Material โดยไม่ต้องย้ายข้อมูล

ในการเลิกบล็อกเวอร์ชันไลบรารีสำหรับการอัปเกรด คุณสามารถใช้ LocalUseFallbackRippleImplementation CompositionLocal API ที่จะกำหนดค่า คอมโพเนนต์วัสดุที่จะกลับไปใช้ลักษณะการทำงานเดิม

CompositionLocalProvider(LocalUseFallbackRippleImplementation provides true) {
    MaterialTheme {
        App()
    }
}

อย่าลืมใส่ข้อมูลนี้นอก MaterialTheme เพื่อให้ Ripples เดิมสามารถ จาก LocalIndication

ส่วนต่อไปนี้จะอธิบายวิธีย้ายข้อมูลไปยัง API ใหม่

ย้ายข้อมูลจาก rememberRipple ไปยัง ripple

การใช้ไลบรารี Material

หากคุณใช้ไลบรารี Material ให้แทนที่ rememberRipple() โดยตรงด้วย การเรียกไปยัง ripple() จากไลบรารีที่เกี่ยวข้อง API นี้สร้างระลอกคลื่น โดยใช้ค่าที่ได้จาก API ธีม Material จากนั้นส่งคำสั่งซื้อที่ส่งคืน ออบเจ็กต์ Modifier.clickable และ/หรือคอมโพเนนต์อื่นๆ

ตัวอย่างเช่น ข้อมูลโค้ดต่อไปนี้ใช้ API ที่เลิกใช้งานแล้ว

Box(
    Modifier.clickable(
        onClick = {},
        interactionSource = remember { MutableInteractionSource() },
        indication = rememberRipple()
    )
) {
    // ...
}

คุณควรแก้ไขข้อมูลโค้ดด้านบนเป็น

@Composable
private fun RippleExample() {
    Box(
        Modifier.clickable(
            onClick = {},
            interactionSource = remember { MutableInteractionSource() },
            indication = ripple()
        )
    ) {
        // ...
    }
}

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

การใช้ระบบการออกแบบที่กำหนดเอง

หากคุณติดตั้งระบบออกแบบของคุณเอง โดยก่อนหน้านี้คุณใช้ rememberRipple() พร้อมด้วย RippleTheme ที่กำหนดเองเพื่อกำหนดค่าคลื่น คุณควรระบุ Ripple API ของคุณเองที่มอบสิทธิ์ให้กับโหนด Ripple แทน API เปิดเผยใน material-ripple จากนั้นคอมโพเนนต์สามารถใช้ระลอกคลื่นของคุณเองได้ ที่ใช้ค่าธีมของคุณโดยตรง สำหรับข้อมูลเพิ่มเติม โปรดดูที่ย้ายข้อมูล จากRippleTheme

ย้ายข้อมูลจาก RippleTheme

เลือกไม่ใช้การเปลี่ยนแปลงลักษณะการทำงานชั่วคราว

ไลบรารีเนื้อหามี CompositionLocal ชั่วคราว LocalUseFallbackRippleImplementation ซึ่งใช้เพื่อกำหนดค่าทั้งหมด คอมโพเนนต์วัสดุที่จะกลับไปใช้ rememberRipple ด้วยวิธีนี้ rememberRipple ยังคงค้นหา LocalRippleTheme ต่อไป

ข้อมูลโค้ดต่อไปนี้สาธิตวิธีใช้ API ของ LocalUseFallbackRippleImplementation CompositionLocal:

CompositionLocalProvider(LocalUseFallbackRippleImplementation provides true) {
    MaterialTheme {
        App()
    }
}

หากคุณใช้ธีมแอปที่กำหนดเองซึ่งสร้างขึ้นจาก Material ให้ทำดังนี้ ใส่องค์ประกอบไว้ภายในธีมของแอปอย่างปลอดภัย:

@OptIn(ExperimentalMaterialApi::class)
@Composable
fun MyAppTheme(content: @Composable () -> Unit) {
    CompositionLocalProvider(LocalUseFallbackRippleImplementation provides true) {
        MaterialTheme(content = content)
    }
}

โปรดดูข้อมูลเพิ่มเติมที่หัวข้ออัปเกรดเวอร์ชันไลบรารี Material โดยไม่มี การย้ายข้อมูล

ใช้ RippleTheme เพื่อปิดใช้ระลอกคลื่นของคอมโพเนนต์ที่กำหนด

ไลบรารี material และ material3 แสดง RippleConfiguration และ LocalRippleConfiguration ซึ่งช่วยให้คุณกำหนดค่าลักษณะที่ปรากฏของ ระลอกคลื่นภายในต้นไม้ย่อย โปรดทราบว่า RippleConfiguration และ LocalRippleConfiguration เป็นเวอร์ชันทดลองและมีไว้สำหรับ 1 คอมโพเนนต์เท่านั้น การปรับแต่งได้มากขึ้น การกำหนดค่าส่วนกลาง/ทั้งธีมยังไม่รองรับรายการต่อไปนี้ API; ดูการใช้ RippleTheme เพื่อเปลี่ยนแปลง Ripples ทั้งหมดทั่วโลกใน สำหรับข้อมูลเพิ่มเติมเกี่ยวกับกรณีการใช้งานดังกล่าว

ตัวอย่างเช่น ข้อมูลโค้ดต่อไปนี้ใช้ API ที่เลิกใช้งานแล้ว

private object DisabledRippleTheme : RippleTheme {

    @Composable
    override fun defaultColor(): Color = Color.Transparent

    @Composable
    override fun rippleAlpha(): RippleAlpha = RippleAlpha(0f, 0f, 0f, 0f)
}

// ...
    CompositionLocalProvider(LocalRippleTheme provides DisabledRippleTheme) {
        Button {
            // ...
        }
    }

คุณควรแก้ไขข้อมูลโค้ดด้านบนเป็น

CompositionLocalProvider(LocalRippleConfiguration provides null) {
    Button {
        // ...
    }
}

ใช้ RippleTheme เพื่อเปลี่ยนสี/อัลฟ่าของระลอกคลื่นสำหรับคอมโพเนนต์ที่กำหนด

ตามที่อธิบายไว้ในส่วนก่อนหน้านี้ RippleConfiguration และ LocalRippleConfiguration เป็น API ทดลองและมีไว้สำหรับ การปรับแต่งต่อคอมโพเนนต์

ตัวอย่างเช่น ข้อมูลโค้ดต่อไปนี้ใช้ API ที่เลิกใช้งานแล้ว

private object DisabledRippleThemeColorAndAlpha : RippleTheme {

    @Composable
    override fun defaultColor(): Color = Color.Red

    @Composable
    override fun rippleAlpha(): RippleAlpha = MyRippleAlpha
}

// ...
    CompositionLocalProvider(LocalRippleTheme provides DisabledRippleThemeColorAndAlpha) {
        Button {
            // ...
        }
    }

คุณควรแก้ไขข้อมูลโค้ดด้านบนเป็น

@OptIn(ExperimentalMaterialApi::class)
private val MyRippleConfiguration =
    RippleConfiguration(color = Color.Red, rippleAlpha = MyRippleAlpha)

// ...
    CompositionLocalProvider(LocalRippleConfiguration provides MyRippleConfiguration) {
        Button {
            // ...
        }
    }

ใช้ RippleTheme เพื่อเปลี่ยนแปลง Ripples ทั้งหมดในแอปพลิเคชัน

ก่อนหน้านี้ คุณสามารถใช้ LocalRippleTheme เพื่อกำหนดลักษณะระลอกคลื่นที่ ในระดับธีมทั้งหมด โดยเป็นจุดผสานรวมระหว่างโซลูชัน องค์ประกอบของระบบการออกแบบ ในพื้นที่และระลอกคลื่น แทนที่จะแสดงโฆษณาทั่วไป ธีมพื้นฐาน material-ripple จะแสดง createRippleModifierNode() ฟังก์ชันนี้ช่วยให้ไลบรารีระบบการออกแบบสร้าง ติดตั้งใช้งาน wrapper ที่ค้นหาค่าธีมแล้วมอบสิทธิ์ การติดตั้งใช้งานระลอกคลื่นไปยังโหนดที่สร้างโดยฟังก์ชันนี้

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

ดูคำแนะนำได้ที่การใช้งาน Ripple API ใน Material ไลบรารี และแทนที่การเรียกไปยังคนท้องถิ่นในการจัดองค์ประกอบเนื้อหาตามที่จำเป็นสำหรับ ระบบการออกแบบของคุณเอง

ย้ายข้อมูลจาก Indication ไปยัง IndicationNodeFactory

ขับผ่าน Indication

หากคุณเพียงแค่สร้าง Indication เพื่อส่งต่อ เช่น สร้าง ระลอกคลื่นเพื่อส่งไปยัง Modifier.clickable หรือ Modifier.indication คุณไม่ต้อง ต้องทำการเปลี่ยนแปลงอะไร IndicationNodeFactory รับค่าจาก Indication เพื่อให้ทุกอย่างคอมไพล์และทำงานได้ต่อไป

กำลังสร้าง Indication

หากคุณสร้างการใช้งาน Indication ของคุณเอง การย้ายข้อมูลควร เรียบง่ายในกรณีส่วนใหญ่ ตัวอย่างเช่น ลองพิจารณา Indication ที่ใช้พารามิเตอร์ ปรับผลเมื่อกด:

object ScaleIndication : Indication {
    @Composable
    override fun rememberUpdatedInstance(interactionSource: InteractionSource): IndicationInstance {
        // key the remember against interactionSource, so if it changes we create a new instance
        val instance = remember(interactionSource) { ScaleIndicationInstance() }

        LaunchedEffect(interactionSource) {
            interactionSource.interactions.collectLatest { interaction ->
                when (interaction) {
                    is PressInteraction.Press -> instance.animateToPressed(interaction.pressPosition)
                    is PressInteraction.Release -> instance.animateToResting()
                    is PressInteraction.Cancel -> instance.animateToResting()
                }
            }
        }

        return instance
    }
}

private class ScaleIndicationInstance : IndicationInstance {
    var currentPressPosition: Offset = Offset.Zero
    val animatedScalePercent = Animatable(1f)

    suspend fun animateToPressed(pressPosition: Offset) {
        currentPressPosition = pressPosition
        animatedScalePercent.animateTo(0.9f, spring())
    }

    suspend fun animateToResting() {
        animatedScalePercent.animateTo(1f, spring())
    }

    override fun ContentDrawScope.drawIndication() {
        scale(
            scale = animatedScalePercent.value,
            pivot = currentPressPosition
        ) {
            this@drawIndication.drawContent()
        }
    }
}

คุณย้ายข้อมูลได้ใน 2 ขั้นตอน

  1. ย้ายข้อมูล ScaleIndicationInstance เป็น DrawModifierNode แพลตฟอร์ม API สำหรับ DrawModifierNode คล้ายกับ IndicationInstance มาก โดยจะแสดง ContentDrawScope#draw() ที่มีฟังก์ชันการทำงานเทียบเท่ากับ IndicationInstance#drawContent() คุณต้องเปลี่ยนฟังก์ชันนั้น จากนั้น ใช้ตรรกะ collectLatest ภายในโหนดโดยตรง แทนฟังก์ชัน Indication

    ตัวอย่างเช่น ข้อมูลโค้ดต่อไปนี้ใช้ API ที่เลิกใช้งานแล้ว

    private class ScaleIndicationInstance : IndicationInstance {
        var currentPressPosition: Offset = Offset.Zero
        val animatedScalePercent = Animatable(1f)
    
        suspend fun animateToPressed(pressPosition: Offset) {
            currentPressPosition = pressPosition
            animatedScalePercent.animateTo(0.9f, spring())
        }
    
        suspend fun animateToResting() {
            animatedScalePercent.animateTo(1f, spring())
        }
    
        override fun ContentDrawScope.drawIndication() {
            scale(
                scale = animatedScalePercent.value,
                pivot = currentPressPosition
            ) {
                this@drawIndication.drawContent()
            }
        }
    }

    คุณควรแก้ไขข้อมูลโค้ดด้านบนเป็น

    private class ScaleIndicationNode(
        private val interactionSource: InteractionSource
    ) : Modifier.Node(), DrawModifierNode {
        var currentPressPosition: Offset = Offset.Zero
        val animatedScalePercent = Animatable(1f)
    
        private suspend fun animateToPressed(pressPosition: Offset) {
            currentPressPosition = pressPosition
            animatedScalePercent.animateTo(0.9f, spring())
        }
    
        private suspend fun animateToResting() {
            animatedScalePercent.animateTo(1f, spring())
        }
    
        override fun onAttach() {
            coroutineScope.launch {
                interactionSource.interactions.collectLatest { interaction ->
                    when (interaction) {
                        is PressInteraction.Press -> animateToPressed(interaction.pressPosition)
                        is PressInteraction.Release -> animateToResting()
                        is PressInteraction.Cancel -> animateToResting()
                    }
                }
            }
        }
    
        override fun ContentDrawScope.draw() {
            scale(
                scale = animatedScalePercent.value,
                pivot = currentPressPosition
            ) {
                this@draw.drawContent()
            }
        }
    }

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

    ตัวอย่างเช่น ข้อมูลโค้ดต่อไปนี้ใช้ API ที่เลิกใช้งานแล้ว

    object ScaleIndication : Indication {
        @Composable
        override fun rememberUpdatedInstance(interactionSource: InteractionSource): IndicationInstance {
            // key the remember against interactionSource, so if it changes we create a new instance
            val instance = remember(interactionSource) { ScaleIndicationInstance() }
    
            LaunchedEffect(interactionSource) {
                interactionSource.interactions.collectLatest { interaction ->
                    when (interaction) {
                        is PressInteraction.Press -> instance.animateToPressed(interaction.pressPosition)
                        is PressInteraction.Release -> instance.animateToResting()
                        is PressInteraction.Cancel -> instance.animateToResting()
                    }
                }
            }
    
            return instance
        }
    }

    คุณควรแก้ไขข้อมูลโค้ดด้านบนเป็น

    object ScaleIndicationNodeFactory : IndicationNodeFactory {
        override fun create(interactionSource: InteractionSource): DelegatableNode {
            return ScaleIndicationNode(interactionSource)
        }
    
        override fun hashCode(): Int = -1
    
        override fun equals(other: Any?) = other === this
    }

กำลังใช้ Indication เพื่อสร้าง IndicationInstance

ในกรณีส่วนใหญ่ คุณควรใช้ Modifier.indication เพื่อแสดง Indication สำหรับ คอมโพเนนต์ อย่างไรก็ตาม ในบางกรณีที่เกิดขึ้นไม่บ่อยนัก คุณจะต้องสร้าง IndicationInstance ที่ใช้ rememberUpdatedInstance คุณต้องอัปเดต เพื่อตรวจสอบว่า Indication เป็น IndicationNodeFactory หรือไม่ เพื่อให้คุณ สามารถใช้การติดตั้งที่น้อยกว่า ตัวอย่างเช่น Modifier.indication จะ มอบสิทธิ์ภายในให้กับโหนดที่สร้างขึ้นหากเป็น IndicationNodeFactory ถ้า ไม่ใช่ แต่จะใช้ Modifier.composed เพื่อโทรหา rememberUpdatedInstance