Para melhorar o desempenho da composição de componentes interativos que usam
Modifier.clickable
, apresentamos 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 na
API:
Descontinuado |
Substituição |
---|---|
|
|
|
Em vez disso, novas APIs Observação: neste contexto, "bibliotecas do Material" se refere a |
|
maneiras:
|
Nesta página, descrevemos o impacto da mudança de comportamento e as instruções para migrar para as novas APIs.
Mudança de comportamento
As seguintes versões da biblioteca incluem uma mudança no 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 das bibliotecas Material não usam mais rememberRipple()
. Em vez disso,
elas usam as novas APIs de efeito ripple. Como resultado, eles não consultam LocalRippleTheme
.
Portanto, se você definir LocalRippleTheme
no aplicativo, os componentes do Material Design não usarão esses valores.
As seções a seguir descrevem como migrar para as novas APIs.
Migrar de rememberRipple
para ripple
Usar uma biblioteca do Material
Se você estiver usando uma biblioteca do Material, substitua diretamente rememberRipple()
por uma
chamada para ripple()
da biblioteca correspondente. Essa API cria um efeito de ondulação
usando valores derivados das APIs do tema Material. 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
lembrada. Ele também pode ser reutilizado em vários componentes, assim como
modificadores. Portanto, considere extrair a criação do efeito ripple para um valor de nível superior para
economizar alocações.
Implementação de um sistema de design personalizado
Se você estiver implementando seu próprio sistema de design e usava rememberRipple()
com um RippleTheme
personalizado para configurar o efeito de ondulação, forneça sua própria API de ondulação que delega às APIs de nó de ondulação expostas 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
deRippleTheme
.
Migrar de RippleTheme
Usar RippleTheme
para desativar um efeito ripple em um determinado componente
As bibliotecas material
e material3
expõem RippleConfiguration
e
LocalRippleConfiguration
, que permitem configurar a aparência de
ondulações em uma subárvore. RippleConfiguration
e LocalRippleConfiguration
são experimentais e destinadas apenas à personalização por componente. A personalização global/em todo o tema não é compatível com essas APIs. Consulte Como usar RippleTheme
para mudar globalmente todos os efeitos de ondulação 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:
CompositionLocalProvider(LocalRippleConfiguration provides null) { Button { // ... } }
Usar RippleTheme
para mudar a cor/alfa de um efeito ripple em um determinado componente
Conforme descrito na seção anterior, RippleConfiguration
e LocalRippleConfiguration
são APIs experimentais e destinadas 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 { // ... } }
Como usar RippleTheme
para mudar globalmente todos os efeitos de ondulação em um aplicativo
Antes, era possível usar LocalRippleTheme
para definir o comportamento de ondulação em um
nível de tema. Esse era essencialmente um ponto de integração entre locais de composição do sistema de design
personalizado e o efeito ripple. Em vez de expor uma primitiva de
tematização genérica, 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 consulta os valores do tema e delega a implementação do efeito ripple ao nó criado por essa função.
Isso permite que os sistemas de design consultem diretamente o que precisam e exponham as camadas de temas configuráveis pelo usuário necessárias sem precisar se adequar ao que é fornecido na camada material-ripple
. Essa mudança também torna mais
explícito a qual tema/especificação o efeito ripple está em conformidade, já que é a
própria API ripple que define esse contrato, em vez de ser implicitamente
derivado do tema.
Para orientação, consulte a implementação da API de ondulação nas bibliotecas do Material e substitua as chamadas aos locais de composição do Material conforme necessário para seu próprio sistema de design.
Migrar de Indication
para IndicationNodeFactory
Passando por Indication
Se você estiver apenas criando um Indication
para transmitir, como criar um
efeito de ondulação para transmitir para Modifier.clickable
ou Modifier.indication
, não
é necessário fazer mudanças. IndicationNodeFactory
herda de Indication
,
então tudo vai continuar sendo compilado e funcionando.
Como criar Indication
Se você estiver criando sua própria implementação do Indication
, a migração será simples na maioria dos casos. Por exemplo, considere um Indication
que aplique um efeito de escala na imprensa:
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() } } }
Você pode migrar isso em duas etapas:
Migre
ScaleIndicationInstance
para ser umDrawModifierNode
. A superfície da API paraDrawModifierNode
é muito semelhante aIndicationInstance
: ela expõe uma funçãoContentDrawScope#draw()
que é funcionalmente equivalente aIndicationInstance#drawContent()
. Você precisa mudar essa função e implementar a lógicacollectLatest
diretamente no nó, em vez doIndication
.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 agora é movida para o nó, esse é um objeto de fábrica muito simples cuja única responsabilidade é 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 mostrar Indication
de um
componente. No entanto, no raro caso de você estar criando manualmente um
IndicationInstance
usando rememberUpdatedInstance
, será 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
vai delegar internamente ao nó criado se ele for um IndicationNodeFactory
. Caso
contrário, ele usará Modifier.composed
para chamar rememberUpdatedInstance
.