為提升使用 Modifier.clickable 的互動式元件組合效能,我們推出了新的 API。這些 API 可讓您更有效率地實作 Indication,例如波紋。
androidx.compose.foundation:foundation:1.7.0+ 和 androidx.compose.material:material-ripple:1.7.0+ 包含下列 API 變更:
已淘汰 |
取代選項 |
|---|---|
|
|
|
改用 Material 程式庫提供的新 注意:在本情境中,「Material 程式庫」是指 |
|
:
|
本頁說明行為變更的影響,以及遷移至新 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 元件不會使用這些值。
以下各節將說明如何遷移至新版 API。
從 rememberRipple 遷移至 ripple
使用 Material 程式庫
如果您使用的是 Material 程式庫,請直接將 rememberRipple() 替換為對應程式庫中的 ripple() 呼叫。這個 API 會使用 Material Design 主題 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() } } }
您可以透過下列兩個步驟遷移:
將
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() } } }
遷移
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,以便使用較輕量的實作。舉例來說,如果建立的節點是 IndicationNodeFactory,Modifier.indication 會在內部委派給該節點。如果沒有,則會使用 Modifier.composed 呼叫 rememberUpdatedInstance。