A API Styles oferece uma abordagem declarativa e simplificada para gerenciar mudanças de IU 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 a aplicação de estilos reativos, StyleState atua como uma interface estável e somente leitura que rastreia o estado ativo de um elemento (como o status ativado, pressionado ou focado). Em um StyleScope, é possível acessar isso pela propriedade state para implementar a lógica condicional diretamente nas definições de estilo.
Interação baseada em estado: cursor passado sobre, focado, pressionado, selecionado, ativado, alternado
Os estilos vêm com suporte integrado para interações comuns:
- Pressionado
- Cursor passado sobre
- Selecionado
- Ativado
- Alternado
Também é possível oferecer suporte a estados personalizados. Consulte a seção Estilização de estado personalizado com StyleState para mais informações.
Processar estados de interação com parâmetros de estilo
O exemplo a seguir demonstra a modificação do background e do borderColor em resposta a estados de interação, especificamente mudando para roxo quando o cursor é passado sobre e azul quando focado:
@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 o cursor é passado sobre 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. Talvez você queira criar um LoginButton que herde de GradientButton, mas altere as cores durante as interações, como ser pressionado.
- Para ativar as atualizações de estilo
interactionSource, inclua uminteractionSourcecomo um parâmetro no elemento combinável. Use o parâmetro fornecido ou, se não houver um, 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 = rememberUpdatedStyleState(interactionSource) { it.isEnabled = enabled } Row( modifier = modifier .clickable( onClick = onClick, enabled = enabled, interactionSource = interactionSource, indication = null, ) .styleable(styleState, baseGradientButtonStyle then style), content = content, ) }
Agora é possível usar o estado interactionSource para gerar modificações de estilo com as opções pressionado, focado e cursor passado sobre no 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. É possível 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 focado:
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 a forma da curva de animação. O exemplo a seguir anima o tamanho da caixa com uma especificação 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)) }
Estilização de estado personalizado com StyleState
Dependendo do caso de uso combinável, você pode ter estilos diferentes com suporte de 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 baseado em 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 recebido. 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)) { ///.. } }
Na expressão lambda style, é possível aplicar a estilização baseada em 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) }
O código a seguir é 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) }