L'API Styles offre un approccio dichiarativo e semplificato alla gestione delle modifiche all'interfaccia utente durante gli stati di interazione come hovered, focused e pressed. Con
questa API, puoi ridurre notevolmente il codice boilerplate in genere richiesto
quando utilizzi i modificatori.
Per facilitare lo stile reattivo, StyleState funge da interfaccia stabile e di sola lettura che tiene traccia dello stato attivo di un elemento (ad esempio il suo stato abilitato, premuto o selezionato). All'interno di un StyleScope, puoi accedere a questa funzionalità tramite la proprietà state per implementare la logica condizionale direttamente nelle definizioni di stile.
Interazione basata sullo stato: al passaggio del mouse, stato attivo, premuto, selezionato, attivato, attivato/disattivato
Gli stili sono dotati di supporto integrato per le interazioni comuni:
- Premuti
- Al passaggio del mouse
- Selezionato
- Abilitati
- Attivato/disattivato
È anche possibile supportare stati personalizzati. Per saperne di più, consulta la sezione Stili personalizzati degli stati con StyleState.
Gestire gli stati di interazione con i parametri di stile
L'esempio seguente mostra la modifica di background e borderColor
in risposta agli stati di interazione, in particolare il passaggio al colore viola quando viene passato il mouse sopra
e al blu quando viene selezionato:
@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) }) } ) }
Puoi anche creare definizioni di stato nidificate. Ad esempio, puoi definire uno stile specifico per quando un pulsante viene premuto e passato con il mouse contemporaneamente:
@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) }) } ) }
Composable personalizzabili con Modifier.styleable
Quando crei i tuoi componenti styleable, devi collegare un
interactionSource a un styleState. Poi, passa questo stato a
Modifier.styleable per utilizzarlo.
Considera uno scenario in cui il tuo sistema di progettazione include un GradientButton. Potresti voler creare un LoginButton che eredita da GradientButton, ma che cambia colore durante le interazioni, ad esempio quando viene premuto.
- Per attivare gli aggiornamenti dello stile
interactionSource, includi uninteractionSourcecome parametro all'interno del tuo elemento componibile. Utilizza il parametro fornito o, se non ne viene fornito uno, inizializza un nuovoMutableInteractionSource. - Inizializza
styleStatefornendointeractionSource. Assicurati che lo stato attivato distyleStaterifletta il valore del parametro attivato fornito. - Assegna
interactionSourceai modificatorifocusableeclickable. Infine, applicastyleStateal parametrostyleabledel modificatore.
@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, ) }
Ora puoi utilizzare lo stato interactionSource per apportare modifiche allo stile con le opzioni premuto, selezionato e al passaggio del mouse all'interno del blocco di stile:
@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.Animare le modifiche dello stile
Le modifiche dello stato degli stili sono dotate di supporto integrato per le animazioni. Puoi racchiudere la nuova proprietà all'interno di qualsiasi blocco di cambio di stato con animate per aggiungere automaticamente animazioni tra i diversi stati. Questo scenario è simile a quello delle API animate*AsState. L'esempio seguente anima borderColor da nero ad azzurro quando
lo stato cambia in attivo:
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)) { } }
L'API animate accetta un animationSpec per modificare la durata o la forma
della curva di animazione. L'esempio seguente anima le dimensioni della casella con una
specifica 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)) }
Stili personalizzati degli stati con StyleState
A seconda del caso d'uso componibile, potresti avere stili diversi supportati da stati personalizzati. Ad esempio, se hai un'app multimediale, potresti voler
avere uno stile diverso per i pulsanti nel composable MediaPlayer
a seconda dello stato di riproduzione del player. Per creare e utilizzare
il tuo stato personalizzato:
- Definisci una chiave personalizzata
- Crea estensione
StyleState - Link allo stato personalizzato
Definisci una chiave personalizzata
Per creare uno stile personalizzato basato sullo stato, crea prima un
StyleStateKey e passa il valore di stato predefinito. Quando l'app viene avviata, il lettore multimediale è nello stato Stopped, quindi viene inizializzato in questo modo:
enum class PlayerState { Stopped, Playing, Paused } val playerStateKey = StyleStateKey(PlayerState.Stopped)
Crea funzioni di estensione StyleState
Definisci una funzione di estensione su StyleState per eseguire query sul playState corrente.
Quindi, crea funzioni di estensione su StyleScope con i tuoi stati personalizzati che passano
in playStateKey, una lambda con lo stato specifico e lo stile.
// 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 allo stato personalizzato
Definisci styleState nel componente e imposta styleState.playState
uguale allo stato in entrata. Passa styleState alla funzione styleable nel
modificatore.
@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)) { ///.. } }
All'interno della lambda style, puoi applicare uno stile basato sullo stato per gli stati personalizzati,
utilizzando le funzioni di estensione definite in precedenza.
@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) }
Il seguente codice è lo snippet completo per questo esempio:
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) }