Per migliorare le prestazioni di composizione dei componenti interattivi che utilizzano
Modifier.clickable
, abbiamo introdotto nuove API. Queste API consentono implementazioni Indication
più efficienti, come gli increspamenti.
androidx.compose.foundation:foundation:1.7.0+
e
androidx.compose.material:material-ripple:1.7.0+
includono le seguenti modifiche
all'API:
Deprecato |
Sostituzione |
---|---|
|
|
|
Nuove API Nota: in questo contesto, "Librerie di materiali" si riferisce a |
|
Procedi in uno dei seguenti modi:
|
Questa pagina descrive l'impatto della modifica del comportamento e fornisce istruzioni per la migrazione alle nuove API.
Modifica del comportamento
Le seguenti versioni della libreria includono una modifica del comportamento ripple:
androidx.compose.material:material:1.7.0+
androidx.compose.material3:material3:1.3.0+
androidx.wear.compose:compose-material:1.4.0+
Queste versioni delle librerie Material non utilizzano più rememberRipple()
, ma le nuove API ripple. Di conseguenza, non eseguono query su LocalRippleTheme
.
Pertanto, se imposti LocalRippleTheme
nella tua applicazione, Material
components will not use these values.
Le sezioni seguenti descrivono come eseguire la migrazione alle nuove API.
Esegui la migrazione da rememberRipple
a ripple
Utilizzo di una libreria di materiali
Se utilizzi una libreria Material, sostituisci direttamente rememberRipple()
con una
chiamata a ripple()
dalla libreria corrispondente. Questa API crea un effetto increspatura
utilizzando valori derivati dalle API del tema Material. Quindi, passa l'oggetto restituito a Modifier.clickable
e/o ad altri componenti.
Ad esempio, lo snippet seguente utilizza le API deprecate:
Box( Modifier.clickable( onClick = {}, interactionSource = remember { MutableInteractionSource() }, indication = rememberRipple() ) ) { // ... }
Devi modificare lo snippet precedente come segue:
@Composable private fun RippleExample() { Box( Modifier.clickable( onClick = {}, interactionSource = remember { MutableInteractionSource() }, indication = ripple() ) ) { // ... } }
Tieni presente che ripple()
non è più una funzione componibile e non deve essere
memorizzata. Può anche essere riutilizzato in più componenti, in modo simile ai modificatori, quindi valuta la possibilità di estrarre la creazione dell'increspatura in un valore di primo livello per risparmiare allocazioni.
Implementazione di un sistema di progettazione personalizzato
Se stai implementando un sistema di progettazione personalizzato e in precedenza utilizzavi
rememberRipple()
insieme a un RippleTheme
personalizzato per configurare l'effetto increspatura,
devi invece fornire la tua API ripple che delega alle API del nodo ripple
esposte in material-ripple
. In questo modo, i componenti possono utilizzare il tuo ripple
che consuma direttamente i valori del tema. Per maggiori informazioni, consulta la pagina Eseguire la migrazione
daRippleTheme
.
Esegui la migrazione da RippleTheme
Utilizzo di RippleTheme
per disattivare un'increspatura per un determinato componente
Le librerie material
e material3
espongono RippleConfiguration
e
LocalRippleConfiguration
, che consentono di configurare l'aspetto
delle increspature all'interno di un sottoalbero. Tieni presente che RippleConfiguration
e
LocalRippleConfiguration
sono sperimentali e destinate solo alla personalizzazione
per componente. La personalizzazione globale/a livello di tema non è supportata da queste
API. Per ulteriori informazioni su questo caso d'uso, consulta Utilizzo di RippleTheme
per modificare globalmente tutti gli effetti ripple in un'applicazione.
Ad esempio, lo snippet seguente utilizza le API deprecate:
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 { // ... } }
Devi modificare lo snippet precedente come segue:
CompositionLocalProvider(LocalRippleConfiguration provides null) { Button { // ... } }
Utilizzo di RippleTheme
per modificare il colore/alpha di un'increspatura per un determinato componente
Come descritto nella sezione precedente, RippleConfiguration
e
LocalRippleConfiguration
sono API sperimentali e sono destinate solo alla
personalizzazione per componente.
Ad esempio, lo snippet seguente utilizza le API deprecate:
private object DisabledRippleThemeColorAndAlpha : RippleTheme { @Composable override fun defaultColor(): Color = Color.Red @Composable override fun rippleAlpha(): RippleAlpha = MyRippleAlpha } // ... CompositionLocalProvider(LocalRippleTheme provides DisabledRippleThemeColorAndAlpha) { Button { // ... } }
Devi modificare lo snippet precedente come segue:
@OptIn(ExperimentalMaterialApi::class) private val MyRippleConfiguration = RippleConfiguration(color = Color.Red, rippleAlpha = MyRippleAlpha) // ... CompositionLocalProvider(LocalRippleConfiguration provides MyRippleConfiguration) { Button { // ... } }
Utilizzo di RippleTheme
per modificare globalmente tutti gli increspamenti in un'applicazione
In precedenza, potevi utilizzare LocalRippleTheme
per definire il comportamento dell'effetto increspatura a livello di tema. Si trattava essenzialmente di un punto di integrazione tra le variabili locali di composizione del sistema di progettazione personalizzato e Ripple. Anziché esporre una primitiva di temi generica, material-ripple
ora espone una funzione createRippleModifierNode()
. Questa funzione consente alle librerie del sistema di progettazione di creare un'implementazione wrapper
di ordine superiore, che esegue query sui valori del tema e delega l'implementazione dell'effetto increspatura al nodo creato da questa funzione.
In questo modo, i sistemi di progettazione possono eseguire query dirette su ciò di cui hanno bisogno ed esporre tutti i livelli di temi configurabili dall'utente richiesti senza dover rispettare ciò che viene fornito a livello di material-ripple
. Questa modifica rende anche più
esplicito il tema/la specifica a cui si conforma l'increspatura, in quanto è
l'API ripple stessa a definire il contratto, anziché essere derivato implicitamente
dal tema.
Per indicazioni, consulta l'implementazione dell'API ripple nelle librerie Material e sostituisci le chiamate ai locali di composizione Material in base alle esigenze del tuo sistema di progettazione.
Esegui la migrazione da Indication
a IndicationNodeFactory
Passaggio intorno alle ore Indication
Se stai solo creando un Indication
da condividere, ad esempio un
ripple da inviare a Modifier.clickable
o Modifier.indication
, non
devi apportare alcuna modifica. IndicationNodeFactory
eredita da Indication
,
quindi tutto continuerà a essere compilato e a funzionare.
Creazione di Indication
in corso…
Se crei la tua implementazione di Indication
, la migrazione dovrebbe
essere semplice nella maggior parte dei casi. Ad esempio, considera un Indication
che applica un
effetto di ridimensionamento alla pressione:
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() } } }
Puoi eseguire la migrazione in due passaggi:
Esegui la migrazione di
ScaleIndicationInstance
per diventare unDrawModifierNode
. La superficie API perDrawModifierNode
è molto simile aIndicationInstance
: espone una funzioneContentDrawScope#draw()
funzionalmente equivalente aIndicationInstance#drawContent()
. Devi modificare questa funzione e poi implementare la logicacollectLatest
direttamente all'interno del nodo, anziché inIndication
.Ad esempio, lo snippet seguente utilizza le API deprecate:
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() } } }
Devi modificare lo snippet precedente come segue:
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() } } }
Esegui la migrazione di
ScaleIndication
per implementareIndicationNodeFactory
. Poiché la logica di raccolta è ora spostata nel nodo, si tratta di un oggetto factory molto semplice la cui unica responsabilità è creare un'istanza del nodo.Ad esempio, lo snippet seguente utilizza le API deprecate:
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 } }
Devi modificare lo snippet precedente come segue:
object ScaleIndicationNodeFactory : IndicationNodeFactory { override fun create(interactionSource: InteractionSource): DelegatableNode { return ScaleIndicationNode(interactionSource) } override fun hashCode(): Int = -1 override fun equals(other: Any?) = other === this }
Utilizzare Indication
per creare un IndicationInstance
Nella maggior parte dei casi, devi utilizzare Modifier.indication
per visualizzare Indication
per un
componente. Tuttavia, nel raro caso in cui crei manualmente un
IndicationInstance
utilizzando rememberUpdatedInstance
, devi aggiornare l'implementazione
per verificare se Indication
è un IndicationNodeFactory
in modo da
poter utilizzare un'implementazione più leggera. Ad esempio, Modifier.indication
delegherà internamente al nodo creato se si tratta di un IndicationNodeFactory
. In caso
contrario, utilizzerà Modifier.composed
per chiamare rememberUpdatedInstance
.