L'API Styles offre un approccio dichiarativo e semplificato alla gestione delle modifiche dell'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 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 accedervi tramite la proprietà state per implementare la logica condizionale direttamente nelle definizioni di stile.
Interazione basata sullo stato: puntatore su, selezionato, premuto, abilitato, attivato/disattivato
Gli stili sono dotati di supporto integrato per le interazioni comuni:
- Premuto
- Puntatore su
- Selezionato
- Abilitato
- Attivato/disattivato
È anche possibile supportare stati personalizzati. Per ulteriori informazioni, consulta la sezione Stile dello stato personalizzato con StyleState.
Gestire gli stati di interazione con i parametri di stile
L'esempio seguente mostra come modificare background e borderColor in risposta agli stati di interazione, in particolare passando al viola quando il puntatore è sopra e al blu quando è 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 il puntatore è sopra 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) }) } ) }
Componibili personalizzati con Modifier.styleable
Quando crei i tuoi componenti styleable, devi collegare un interactionSource a un styleState. Quindi, 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 ne modifica i colori durante le interazioni, ad esempio quando viene premuto.
- Per abilitare gli aggiornamenti dello stile
interactionSource, includi uninteractionSourcecome parametro all'interno del tuo componibile. Utilizza il parametro fornito o, se non ne viene fornito uno, inizializza un nuovoMutableInteractionSource. - Inizializza
styleStatefornendointeractionSource. Assicurati che lo stato abilitato distyleStaterifletta il valore del parametro abilitato 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 = rememberUpdatedStyleState(interactionSource) { it.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 guidare le modifiche dello stile con le opzioni premuto, selezionato e puntatore su 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 modifica dello stato con animate per aggiungere automaticamente le animazioni tra stati diversi. È simile alle API animate*AsState. L'esempio seguente anima borderColor da nero a blu quando lo stato passa a selezionato:
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)) }
Stile dello stato personalizzato con StyleState
A seconda del caso d'uso del 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 tuo componibile MediaPlayer a seconda dello stato di riproduzione del player. Segui questi passaggi per creare e utilizzare il tuo stato personalizzato:
- Definisci chiave personalizzata
- Crea estensione
StyleState - Collega allo stato personalizzato
Definisci 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 media player è 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 una query su playState corrente.
Quindi, crea funzioni di estensione su StyleScope con i tuoi stati personalizzati passando playStateKey, un'espressione 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 }) }
Collega allo stato personalizzato
Definisci il styleState nel tuo componibile e imposta il styleState.playState
uguale allo stato in entrata. Passa styleState alla funzione styleable sul 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 dell'espressione lambda style, puoi applicare lo 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) }