L'API Styles offre une approche déclarative et simplifiée pour gérer les modifications de l'interface utilisateur lors d'états d'interaction tels que hovered, focused et pressed. Avec cette API, vous pouvez réduire considérablement le code récurrent généralement requis lors de l'utilisation de modificateurs.
Pour faciliter l'application de styles réactifs, StyleState fait office d'interface stable en lecture seule qui suit l'état actif d'un élément (par exemple, son état activé, appuyé ou sélectionné). Dans un StyleScope, vous pouvez y accéder via la propriété state pour implémenter une logique conditionnelle directement dans vos définitions de style.
Interaction basée sur l'état : survolé, sélectionné, appuyé, activé, désactivé
Les styles sont compatibles avec les interactions courantes :
- Appuyé
- Survolé
- Sélectionné
- Activé
- Désactivé
Il est également possible de prendre en charge des états personnalisés. Pour en savoir plus, consultez la section Application de styles personnalisés avec StyleState.
Gérer les états d'interaction avec les paramètres de style
L'exemple suivant montre comment modifier background et borderColor en réponse aux états d'interaction, en passant au violet lorsque l'élément est survolé et au bleu lorsqu'il est sélectionné :
@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) }) } ) }
Vous pouvez également créer des définitions d'état imbriquées. Par exemple, vous pouvez définir un style spécifique lorsqu'un bouton est à la fois appuyé et survolé :
@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) }) } ) }
Composables personnalisés avec Modifier.styleable
Lorsque vous créez vos propres composants styleable, vous devez connecter un interactionSource à un styleState. Transmettez ensuite cet état à Modifier.styleable pour l'utiliser.
Prenons l'exemple d'un système de conception qui inclut un GradientButton. Vous pouvez créer un LoginButton qui hérite de GradientButton, mais qui modifie ses couleurs lors des interactions, par exemple lorsqu'il est appuyé.
- Pour activer les mises à jour de style
interactionSource, incluez uninteractionSourceen tant que paramètre dans votre composable. Utilisez le paramètre fourni ou, si aucun n'est fourni, initialisez un nouveauMutableInteractionSource. - Initialisez le
styleStateen fournissant leinteractionSource. Assurez-vous que l'état activé dustyleStatereflète la valeur du paramètre activé fourni. - Attribuez le
interactionSourceaux modificateursfocusableetclickable. Enfin, appliquez lestyleStateau paramètrestyleabledu modificateur.
@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, ) }
Vous pouvez maintenant utiliser l'état interactionSource pour piloter les modifications de style avec les options appuyé, sélectionné et survolé dans le bloc de style :
@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.Animer les modifications de style
Les modifications d'état des styles sont compatibles avec l'animation intégrée. Vous pouvez encapsuler la nouvelle propriété dans n'importe quel bloc de modification d'état avec animate pour ajouter automatiquement des animations entre différents états. Cela est semblable aux API animate*AsState. L'exemple suivant anime le borderColor du noir au bleu lorsque l'état passe à sélectionné :
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 accepte un animationSpec pour modifier la durée ou la forme de la courbe d'animation. L'exemple suivant anime la taille de la zone avec une spécification 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)) }
Application de styles personnalisés avec StyleState
Selon votre cas d'utilisation composable, vous pouvez avoir différents styles basés sur des états personnalisés. Par exemple, si vous disposez d'une application multimédia, vous pouvez avoir différents styles pour les boutons de votre composable MediaPlayer en fonction de l'état de lecture du lecteur. Procédez comme suit pour créer et utiliser votre propre état personnalisé :
- Définir une clé personnalisée
- Créer une extension
StyleState - Lier à un état personnalisé
Définir une clé personnalisée
Pour créer un style personnalisé basé sur l'état, commencez par créer un
StyleStateKey et transmettez la valeur d'état par défaut. Lorsque l'application est lancée, le lecteur multimédia est à l'état Stopped. Il est donc initialisé de la manière suivante :
enum class PlayerState { Stopped, Playing, Paused } val playerStateKey = StyleStateKey(PlayerState.Stopped)
Créer des fonctions d'extension StyleState
Définissez une fonction d'extension sur StyleState pour interroger le playState actuel.
Créez ensuite des fonctions d'extension sur StyleScope avec vos états personnalisés en transmettant le playStateKey, un lambda avec l'état spécifique et le style.
// 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 }) }
Lier à un état personnalisé
Définissez le styleState dans votre composable et définissez le styleState.playState
sur l'état entrant. Transmettez styleState à la fonction styleable sur le modificateur.
@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)) { ///.. } }
Dans le lambda style, vous pouvez appliquer un style basé sur l'état pour les états personnalisés à l'aide des fonctions d'extension définies précédemment.
@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) }
Voici l'extrait de code complet pour cet exemple :
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) }