Para melhorar o desempenho de composição de componentes interativos que usam
Modifier.clickable
, introduzimos novas APIs. Essas APIs permitem implementações de Indication
mais
eficientes, como ondulações.
androidx.compose.foundation:foundation:1.7.0+
e androidx.compose.material:material-ripple:1.7.0+
incluem as seguintes mudanças de API:
Descontinuado |
Substituição |
---|---|
|
|
|
Novas APIs Observação: neste contexto, "Bibliotecas de materiais" se refere a |
|
maneiras:
|
Esta página descreve o impacto da mudança de comportamento e instruções para migrar para as novas APIs.
Mudança de comportamento
As seguintes versões da biblioteca incluem uma mudança de comportamento de ondulação:
androidx.compose.material:material:1.7.0+
androidx.compose.material3:material3:1.3.0+
androidx.wear.compose:compose-material:1.4.0+
Essas versões de bibliotecas do Material Design não usam mais rememberRipple()
. Em vez disso,
elas usam as novas APIs de ondulação. Como resultado, elas não consultam LocalRippleTheme
.
Portanto, se você definir LocalRippleTheme
no seu aplicativo, os componentes
do Material Design não usarão esses valores.
A seção a seguir descreve como voltar temporariamente ao comportamento antigo
sem migrar. No entanto, recomendamos migrar para as novas APIs. Para
instruções de migração, consulte Migrar de rememberRipple
para ripple
e as próximas seções.
Fazer upgrade da versão da biblioteca do Material Design sem migrar
Para desbloquear o upgrade de versões da biblioteca, use a API
LocalUseFallbackRippleImplementation CompositionLocal
temporária para configurar
os componentes do Material Design para retornar ao comportamento antigo:
CompositionLocalProvider(LocalUseFallbackRippleImplementation provides true) { MaterialTheme { App() } }
Forneça isso fora do MaterialTheme
para que as ondulações antigas possam
ser fornecidas pelo LocalIndication
.
As seções a seguir descrevem como migrar para as novas APIs.
Migrar de rememberRipple
para ripple
Como usar uma biblioteca do Material Design
Se você estiver usando uma biblioteca do Material Design, substitua rememberRipple()
diretamente por uma
chamada para ripple()
da biblioteca correspondente. Essa API cria uma ondulação
usando valores derivados das APIs de tema do Material Design. Em seguida, transmita o objeto
retornado para Modifier.clickable
e/ou outros componentes.
Por exemplo, o snippet a seguir usa as APIs descontinuadas:
Box( Modifier.clickable( onClick = {}, interactionSource = remember { MutableInteractionSource() }, indication = rememberRipple() ) ) { // ... }
Modifique o snippet acima para:
@Composable private fun RippleExample() { Box( Modifier.clickable( onClick = {}, interactionSource = remember { MutableInteractionSource() }, indication = ripple() ) ) { // ... } }
Observe que ripple()
não é mais uma função combinável e não precisa ser
lembrado. Ela também pode ser reutilizada em vários componentes, semelhante aos
modificadores. Portanto, considere extrair a criação de ondulação para um valor de nível superior para
salvar as alocações.
Implementar um sistema de design personalizado
Se você está implementando seu próprio sistema de design e já estava usando
rememberRipple()
com um RippleTheme
personalizado para configurar a ondulação,
forneça sua própria API de ondulação que delega para as APIs de nó de ondulação
exibidas em material-ripple
. Assim, seus componentes podem usar sua própria ondulação
que consome os valores do tema diretamente. Para mais informações, consulte Migrar
do RippleTheme
.
Migrar do RippleTheme
Desativar temporariamente a mudança de comportamento
As bibliotecas do Material Design têm um CompositionLocal
temporário,
LocalUseFallbackRippleImplementation
, que pode ser usado para configurar todos os
componentes do Material Design para voltar a usar rememberRipple
. Dessa forma,
rememberRipple
continua consultando LocalRippleTheme
.
O snippet de código a seguir demonstra como usar a API
LocalUseFallbackRippleImplementation CompositionLocal
:
CompositionLocalProvider(LocalUseFallbackRippleImplementation provides true) { MaterialTheme { App() } }
Se você estiver usando um tema personalizado do app criado com base no Material Design, poderá fornecer com segurança a composição local como parte do tema do app:
@OptIn(ExperimentalMaterialApi::class) @Composable fun MyAppTheme(content: @Composable () -> Unit) { CompositionLocalProvider(LocalUseFallbackRippleImplementation provides true) { MaterialTheme(content = content) } }
Para ver mais informações, consulte a seção Fazer upgrade da versão da biblioteca do Material Design sem migrar.
Uso de RippleTheme
para desativar uma ondulação em um determinado componente.
As bibliotecas material
e material3
expõem RippleConfiguration
e
LocalRippleConfiguration
, que permitem configurar a aparência das
ondulações em uma subárvore. Observe que RippleConfiguration
e
LocalRippleConfiguration
são experimentais e se destinam apenas à personalização
por componente. A personalização global/do tema não tem suporte para essas
APIs. Consulte Como usar RippleTheme
para mudar globalmente todas as ondulações em um
aplicativo para mais informações sobre esse caso de uso.
Por exemplo, o snippet a seguir usa as APIs descontinuadas:
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 { // ... } }
Modifique o snippet acima para:
@OptIn(ExperimentalMaterialApi::class) private val DisabledRippleConfiguration = RippleConfiguration(isEnabled = false) // ... CompositionLocalProvider(LocalRippleConfiguration provides DisabledRippleConfiguration) { Button { // ... } }
Uso de RippleTheme
para mudar a cor/alfa de uma ondulação em um determinado componente
Conforme descrito na seção anterior, RippleConfiguration
e
LocalRippleConfiguration
são APIs experimentais e se destinam apenas à
personalização por componente.
Por exemplo, o snippet a seguir usa as APIs descontinuadas:
private object DisabledRippleThemeColorAndAlpha : RippleTheme { @Composable override fun defaultColor(): Color = Color.Red @Composable override fun rippleAlpha(): RippleAlpha = MyRippleAlpha } // ... CompositionLocalProvider(LocalRippleTheme provides DisabledRippleThemeColorAndAlpha) { Button { // ... } }
Modifique o snippet acima para:
@OptIn(ExperimentalMaterialApi::class) private val MyRippleConfiguration = RippleConfiguration(color = Color.Red, rippleAlpha = MyRippleAlpha) // ... CompositionLocalProvider(LocalRippleConfiguration provides MyRippleConfiguration) { Button { // ... } }
Uso de RippleTheme
para mudar globalmente todas as ondulações em um app
Anteriormente, era possível usar LocalRippleTheme
para definir o comportamento de ondulação em um
nível do tema. Esse era essencialmente um ponto de integração entre os locais
de composição do sistema de design personalizado e a ondulação. Em vez de expor um primitivo
de tema genérico, o material-ripple
agora expõe uma função
createRippleModifierNode()
. Essa função permite que as bibliotecas do sistema de design criem uma implementação
de wrapper
de ordem superior, que consultam os valores de tema e deleguem
a implementação de ondulação ao nó criado por essa função.
Isso permite que os sistemas de design consultem diretamente o que eles precisam e exponham
as camadas de temas configuráveis pelo usuário na parte de cima sem precisar obedecer
ao que é fornecido na camada material-ripple
. Essa mudança também torna mais
explícito a que tema/especificação a ondulação está em conformidade, já que é a
própria API de ondulação que define esse contrato, em vez de ser implicitamente
derivada do tema.
Para conferir orientações, consulte a implementação da API de ondulação (link em inglês) nas bibliotecas do Material Design e substitua as chamadas para os locais de composição do Material Design conforme necessário para seu próprio sistema de design.
Migrar de Indication
para IndicationNodeFactory
Passando por Indication
Se você está apenas criando um Indication
para transmissão, como uma ondulação para transmitir a Modifier.clickable
ou Modifier.indication
, não é necessário fazer alterações. O IndicationNodeFactory
é herdado de Indication
,
então tudo continuará sendo compilado e funcionando.
Criando Indication
Se você estiver criando sua própria implementação de Indication
, a migração será simples na maioria dos casos. Por exemplo, considere um Indication
que aplica um
efeito de escala ao pressionar:
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() } } }
É possível migrar isso em duas etapas:
Migre
ScaleIndicationInstance
paraDrawModifierNode
. A superfície da API paraDrawModifierNode
é muito semelhante aIndicationInstance
: ela expõe uma funçãoContentDrawScope#draw()
que é funcionalmente equivalente aIndicationInstance#drawContent()
. Você precisa alterar essa função e, em seguida, implementar a lógicacollectLatest
diretamente dentro do nó, em vez deIndication
.Por exemplo, o snippet a seguir usa as APIs descontinuadas:
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() } } }
Modifique o snippet acima para:
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() } } }
Migre
ScaleIndication
para implementarIndicationNodeFactory
. Como a lógica de coleta é movida para o nó, esse é um objeto de fábrica muito simples, com a única responsabilidade de criar uma instância de nó.Por exemplo, o snippet a seguir usa as APIs descontinuadas:
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 } }
Modifique o snippet acima para:
object ScaleIndicationNodeFactory : IndicationNodeFactory { override fun create(interactionSource: InteractionSource): DelegatableNode { return ScaleIndicationNode(interactionSource) } override fun hashCode(): Int = -1 override fun equals(other: Any?) = other === this }
Como usar Indication
para criar um IndicationInstance
Na maioria dos casos, use Modifier.indication
para exibir Indication
para um
componente. No entanto, no raro caso de você estar criando manualmente um
IndicationInstance
usando rememberUpdatedInstance
, é necessário atualizar sua
implementação para verificar se o Indication
é um IndicationNodeFactory
para que você
possa usar uma implementação mais leve. Por exemplo, Modifier.indication
delegará internamente o nó criado se ele for um IndicationNodeFactory
. Caso contrário, ele vai usar Modifier.composed
para chamar rememberUpdatedInstance
.