為改善使用 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 Design 元件就不會使用這些值。
下節說明如何在不遷移的情況下暫時改回舊行為,但我們建議您遷移至新的 API。如需遷移操作說明,請參閱從 rememberRipple
遷移至 ripple
,以及後續章節。
升級 Material 程式庫版本而不遷移
如要解除封鎖升級程式庫版本,您可以使用臨時 LocalUseFallbackRippleImplementation CompositionLocal
API 設定 Material 元件,改回使用舊行為:
CompositionLocalProvider(LocalUseFallbackRippleImplementation provides true) { MaterialTheme { App() } }
請務必在 MaterialTheme
外部提供這項資訊,以便透過 LocalIndication
提供舊的漣漪效果。
以下各節將說明如何遷移至新版 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
」遷移
暫時選擇不採用行為變更
Material 程式庫提供臨時 CompositionLocal
、LocalUseFallbackRippleImplementation
,可用於設定所有 Material 元件,改回使用 rememberRipple
。這樣一來,rememberRipple
會繼續查詢 LocalRippleTheme
。
下列程式碼片段展示如何使用 LocalUseFallbackRippleImplementation CompositionLocal
API:
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
屬於實驗性質,僅適用於個別元件自訂項目。這些 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 { // ... } }
您應該將上述程式碼片段修改為:
@OptIn(ExperimentalMaterialApi::class) private val DisabledRippleConfiguration = RippleConfiguration(isEnabled = false) // ... CompositionLocalProvider(LocalRippleConfiguration provides DisabledRippleConfiguration) { Button { // ... } }
使用 RippleTheme
變更特定元件的漣漪效果顏色/Alpha 值
如上一節所述,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
全域變更應用程式中的所有漣漪效果
您之前可以使用 LocalRippleTheme
定義整個主題層級的漣漪行為。本質上是自訂設計系統組合本機與漣漪的整合點。material-ripple
現在不會公開一般的主題設定原始物件,而是提供 createRippleModifierNode()
函式。這個函式可讓設計系統程式庫建立高順序的 wrapper
實作,以便查詢其主題值,然後將漣漪效果實作委派給這個函式所建立的節點。
這可讓設計系統直接查詢所需內容,並在頂端顯示任何需要由使用者設定的主題設定層,而不需符合 material-ripple
層提供的內容。這項變更也會讓漣漪效果符合的主題/規格更加明確,因為這是定義該合約的漣漪 API,而非間接衍生自主題。
如需指引,請參閱 Material 程式庫中的 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() } } }
您可以按照下列兩個步驟遷移此內容:
將
ScaleIndicationInstance
遷移至DrawModifierNode
。DrawModifierNode
的 API 介面與IndicationInstance
非常類似,也就是會公開功能相當於IndicationInstance#drawContent()
的ContentDrawScope#draw()
函式。您必須變更該函式,然後直接在節點內實作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
。