遷移至 Indication 和 Ripple API

為提升使用 Modifier.clickable 的互動式元件組合效能,我們推出了新的 API。這些 API 可讓您更有效率地Indication實作漣漪等效果。

androidx.compose.foundation:foundation:1.7.0+ 和 androidx.compose.material:material-ripple:1.7.0+ 包含下列 API 變更:

已淘汰

取代選項

Indication#rememberUpdatedInstance

IndicationNodeFactory

rememberRipple()

改為在 Material 程式庫中提供新的 ripple() API。

注意:在此情境中,「Material 程式庫」是指 androidx.compose.material:material、androidx.compose.material3:material3、androidx.wear.compose:compose-material 和 androidx.wear.compose:compose-material3.

RippleTheme

:

  • 使用 Material 程式庫 RippleConfiguration API,或
  • 建構自己的設計系統漣漪實作

本頁說明行為變更的影響,以及如何遷移至新版 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(),而是採用新的漣漪效果 API。因此不會查詢 LocalRippleTheme。 因此,如果您在應用程式中設定 LocalRippleTheme,Material Design 元件不會使用這些值。

以下各節說明如何遷移至新版 API。

從 rememberRipple 遷移至 ripple

使用 Material 程式庫

如果您使用 Material 程式庫,請直接將 rememberRipple() 替換為對應程式庫的 ripple() 呼叫。這個 API 會使用從 Material 主題 API 衍生而來的值建立波紋。然後將傳回的物件傳遞至 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 設定波紋,則應改為提供自己的波紋 API,委派給 material-ripple 中公開的波紋節點 API。然後,元件就能使用自己的波紋,直接取用主題值。詳情請參閱「從RippleTheme遷移」。

從 RippleTheme 遷移

使用 RippleTheme 為特定元件停用漣漪效果

material 和 material3 程式庫會公開 RippleConfiguration 和 LocalRippleConfiguration,讓您設定子樹狀結構中漣漪的外觀。請注意,RippleConfiguration 和 LocalRippleConfiguration 僅適用於個別元件的自訂作業。這些 API 不支援全域/主題範圍的自訂設定;如要進一步瞭解該用途,請參閱「使用 RippleTheme 全域變更應用程式中的所有漣漪效果」。

舉例來說,下列程式碼片段會使用已淘汰的 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 變更特定元件的漣漪顏色/Alpha 值

如上一節所述,RippleConfiguration 和 LocalRippleConfiguration 僅適用於個別元件的自訂作業。

舉例來說,下列程式碼片段會使用已淘汰的 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 全域變更應用程式中的所有漣漪

先前,您可以在主題層級使用 LocalRippleTheme 定義波紋行為。這基本上是自訂設計系統組合本機值與波紋之間的整合點。material-ripple 現在會公開 createRippleModifierNode() 函式,而非公開一般主題設定基本項目。設計系統程式庫可透過這項函式建立高階 wrapper 實作項目,查詢主題值,然後將漣漪實作項目委派給這項函式建立的節點。

設計系統可直接查詢所需內容,並在頂端公開任何必要的使用者可設定主題層,不必遵守 material-ripple 層提供的內容。這項變更也更明確地指出波紋符合的主題/規格,因為定義合約的是波紋 API 本身,而不是從主題隱含衍生。

如需相關指引,請參閱 Material 程式庫中的水波紋 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()
        }
    }
}

您可以透過下列兩個步驟遷移這項資料:

  1. 將 ScaleIndicationInstance 遷移為 DrawModifierNode。DrawModifierNode 的 API 介面與 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。不過,如果使用 rememberUpdatedInstance 手動建立 IndicationInstance,則需要更新導入作業,檢查 Indication 是否為 IndicationNodeFactory,以便使用較輕量的導入作業。舉例來說,如果 Modifier.indication 是 IndicationNodeFactory,系統會在內部委派給建立的節點。如果沒有,系統會使用 Modifier.composed 呼叫 rememberUpdatedInstance。