A API Styles oferece uma abordagem declarativa e simplificada para gerenciar mudanças na interface
durante estados de interação como hovered, focused e pressed. Com
essa API, é possível diminuir significativamente o código boilerplate normalmente necessário
ao usar modificadores.
Para facilitar o estilo reativo, o StyleState atua como uma interface estável e somente leitura que rastreia o estado ativo de um elemento, como o status ativado, pressionado ou em foco. Em um StyleScope, você pode acessar isso pela propriedade state para implementar a lógica condicional diretamente nas definições de estilo.
Interação baseada em estado: passar o cursor, foco, pressionado, selecionado, ativado, alternado
Os estilos vêm com suporte integrado para interações comuns:
- Pressionado
- Passar o cursor do mouse
- Selecionado
- Ativado
- Alternada
Também é possível oferecer suporte a estados personalizados. Consulte a seção Estilização de estado personalizada com StyleState para mais informações.
Processar estados de interação com parâmetros de estilo
O exemplo a seguir demonstra como modificar background e borderColor
em resposta a estados de interação, especificamente mudando para roxo quando passa o cursor
e azul quando está em foco:
@Preview @Composable private fun OpenButton() { BaseButton( style = outlinedButtonStyle then { background(Color.White) hovered { background(lightPurple) border(2.dp, lightPurple) } focused { background(lightBlue) } }, onClick = { }, content = { BaseText("Open in Studio", style = { contentColor(Color.Black) fontSize(26.sp) textAlign(TextAlign.Center) }) } ) }
Também é possível criar definições de estado aninhadas. Por exemplo, é possível definir um estilo específico para quando um botão é pressionado e passa o cursor sobre ele simultaneamente:
@Composable private fun OpenButton_CombinedStates() { BaseButton( style = outlinedButtonStyle then { background(Color.White) hovered { // light purple background(lightPurple) pressed { // When running on a device that can hover, whilst hovering and then pressing the button this would be invoked background(lightOrange) } } pressed { // when running on a device without a mouse attached, this would be invoked as you wouldn't be in a hovered state only background(lightRed) } focused { background(lightBlue) } }, onClick = { }, content = { BaseText("Open in Studio", style = { contentColor(Color.Black) fontSize(26.sp) textAlign(TextAlign.Center) }) } ) }
Elementos combináveis personalizados com Modifier.styleable
Ao criar seus próprios componentes styleable, é necessário conectar um
interactionSource a um styleState. Em seguida, transmita esse estado para Modifier.styleable para usá-lo.
Considere um cenário em que seu sistema de design inclui um GradientButton. Você
pode criar um LoginButton que herda de GradientButton, mas
altera as cores durante as interações, como quando é pressionado.
- Para ativar as atualizações de estilo
interactionSource, inclua uminteractionSourcecomo parâmetro no seu elemento combinável. Use o parâmetro fornecido ou, se nenhum for fornecido, inicialize um novoMutableInteractionSource. - Inicialize o
styleStatefornecendo ointeractionSource. Verifique se o status ativado dostyleStatereflete o valor do parâmetro ativado fornecido. - Atribua o
interactionSourceaos modificadoresfocusableeclickable. Por fim, aplique ostyleStateao parâmetrostyleabledo modificador.
@Composable private fun GradientButton( onClick: () -> Unit, modifier: Modifier = Modifier, style: Style = Style, enabled: Boolean = true, interactionSource: MutableInteractionSource? = null, content: @Composable RowScope.() -> Unit, ) { val interactionSource = interactionSource ?: remember { MutableInteractionSource() } val styleState = remember(interactionSource) { MutableStyleState(interactionSource) } styleState.isEnabled = enabled Row( modifier = modifier .clickable( onClick = onClick, enabled = enabled, interactionSource = interactionSource, indication = null, ) .styleable(styleState, baseGradientButtonStyle then style), content = content, ) }
Agora você pode usar o estado interactionSource para acionar modificações de estilo com
as opções pressionado, em foco e passar o cursor dentro do bloco de estilo:
@Preview @Composable fun LoginButton() { val loginButtonStyle = Style { pressed { background( Brush.linearGradient( listOf(Color.Magenta, Color.Red) ) ) } } GradientButton(onClick = { // Login logic }, style = loginButtonStyle) { BaseText("Login") } }
interactionSource.Animar mudanças de estilo
As mudanças de estado de estilos vêm com suporte integrado para animação. Você pode incluir a nova propriedade em qualquer bloco de mudança de estado com animate para adicionar animações automaticamente entre diferentes estados. Isso é semelhante às APIs animate*AsState. O exemplo a seguir anima o borderColor de preto para azul quando
o estado muda para "em foco":
val animatingStyle = Style { externalPadding(48.dp) border(3.dp, Color.Black) background(Color.White) size(100.dp) pressed { animate { borderColor(Color.Magenta) background(Color(0xFFB39DDB)) } } } @Preview @Composable private fun AnimatingStyleChanges() { val interactionSource = remember { MutableInteractionSource() } val styleState = remember(interactionSource) { MutableStyleState(interactionSource) } Box(modifier = Modifier .clickable( interactionSource, enabled = true, indication = null, onClick = { } ) .styleable(styleState, animatingStyle)) { } }
A API animate aceita um animationSpec para mudar a duração ou o formato da curva de animação. O exemplo a seguir anima o tamanho da caixa com uma especificação de spring:
val animatingStyleSpec = Style { externalPadding(48.dp) border(3.dp, Color.Black) background(Color.White) size(100.dp) transformOrigin(TransformOrigin.Center) pressed { animate { borderColor(Color.Magenta) background(Color(0xFFB39DDB)) } animate(spring(dampingRatio = Spring.DampingRatioMediumBouncy)) { scale(1.2f) } } } @Preview(showBackground = true) @Composable fun AnimatingStyleChangesSpec() { val interactionSource = remember { MutableInteractionSource() } val styleState = remember(interactionSource) { MutableStyleState(interactionSource) } Box(modifier = Modifier .clickable( interactionSource, enabled = true, indication = null, onClick = { } ) .styleable(styleState, animatingStyleSpec)) }
Estilo de estado personalizado com StyleState
Dependendo do seu caso de uso combinável, você pode ter estilos diferentes que são
compatíveis com estados personalizados. Por exemplo, se você tiver um app de mídia, talvez queira
ter estilos diferentes para os botões no elemento combinável MediaPlayer
dependendo do estado de reprodução do player. Siga estas etapas para criar e usar seu próprio estado personalizado:
- Definir chave personalizada
- Criar extensão
StyleState - Link para estado personalizado
Definir chave personalizada
Para criar um estilo personalizado com base no estado, primeiro crie um
StyleStateKey e transmita o valor de estado padrão. Quando o
app é iniciado, o player de mídia está no estado Stopped. Portanto, ele é inicializado
desta forma:
enum class PlayerState { Stopped, Playing, Paused } val playerStateKey = StyleStateKey(PlayerState.Stopped)
Criar funções de extensão StyleState
Defina uma função de extensão em StyleState para consultar o playState atual.
Em seguida, crie funções de extensão em StyleScope com seus estados personalizados transmitindo
o playStateKey, uma expressão lambda com o estado específico e o estilo.
// Extension Function on MutableStyleState to query and set the current playState var MutableStyleState.playerState get() = this[playerStateKey] set(value) { this[playerStateKey] = value } fun StyleScope.playerPlaying(value: Style) { state(playerStateKey, value, { key, state -> state[key] == PlayerState.Playing }) } fun StyleScope.playerPaused(value: Style) { state(playerStateKey, value, { key, state -> state[key] == PlayerState.Paused }) }
Link para estado personalizado
Defina o styleState no elemento combinável e defina o styleState.playState
igual ao estado de entrada. Transmita styleState para a função styleable no modificador.
@Composable fun MediaPlayer( url: String, modifier: Modifier = Modifier, style: Style = Style, state: PlayerState = remember { PlayerState.Paused } ) { // Hoist style state, set playstate as a parameter, val styleState = remember { MutableStyleState(null) } // Set equal to incoming state to link the two together styleState.playerState = state Box( modifier = modifier.styleable(styleState, style)) { ///.. } }
No lambda style, é possível aplicar estilos com base no estado para estados personalizados usando as funções de extensão definidas anteriormente.
@Composable fun StyleStateKeySample() { // Using the extension function to change the border color to green while playing val style = Style { borderColor(Color.Gray) playerPlaying { animate { borderColor(Color.Green) } } playerPaused { animate { borderColor(Color.Blue) } } } val styleState = remember { MutableStyleState(null) } styleState[playerStateKey] = PlayerState.Playing // Using the style in a composable that sets the state -> notice if you change the state parameter, the style changes. You can link this up to an ViewModel and change the state from there too. MediaPlayer(url = "https://example.com/media/video", style = style, state = PlayerState.Stopped) }
Confira abaixo o snippet completo deste exemplo:
enum class PlayerState { Stopped, Playing, Paused } val playerStateKey = StyleStateKey<PlayerState>(PlayerState.Stopped) var MutableStyleState.playerState get() = this[playerStateKey] set(value) { this[playerStateKey] = value } fun StyleScope.playerPlaying(value: Style) { state(playerStateKey, value, { key, state -> state[key] == PlayerState.Playing }) } fun StyleScope.playerPaused(value: Style) { state(playerStateKey, value, { key, state -> state[key] == PlayerState.Paused }) } @Composable fun MediaPlayer( url: String, modifier: Modifier = Modifier, style: Style = Style, state: PlayerState = remember { PlayerState.Paused } ) { // Hoist style state, set playstate as a parameter, val styleState = remember { MutableStyleState(null) } // Set equal to incoming state to link the two together styleState.playerState = state Box( modifier = modifier.styleable(styleState, Style { size(100.dp) border(2.dp, Color.Red) }, style, )) { ///.. } } @Composable fun StyleStateKeySample() { // Using the extension function to change the border color to green while playing val style = Style { borderColor(Color.Gray) playerPlaying { animate { borderColor(Color.Green) } } playerPaused { animate { borderColor(Color.Blue) } } } val styleState = remember { MutableStyleState(null) } styleState[playerStateKey] = PlayerState.Playing // Using the style in a composable that sets the state -> notice if you change the state parameter, the style changes. You can link this up to an ViewModel and change the state from there too. MediaPlayer(url = "https://example.com/media/video", style = style, state = PlayerState.Stopped) }