L'API Styles offre une approche déclarative et simplifiée pour gérer les modifications de l'UI lors des états d'interaction tels que hovered, focused et pressed. Cette API vous permet de réduire considérablement le code récurrent généralement requis lors de l'utilisation de modificateurs.
Pour faciliter le style réactif, StyleState sert 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é
- Pointeur sur
- Sélectionné
- Activé
- Activé
Il est également possible de prendre en charge des états personnalisés. Pour en savoir plus, consultez la section Personnaliser le style des états 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 plus précisément au violet en cas de pointeur et au bleu en cas de sélection :
@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 pour un bouton lorsque l'utilisateur appuie dessus et le survole simultanément :
@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. Ensuite, transmettez cet état à Modifier.styleable pour l'utiliser.
Imaginons que votre système de conception inclue un GradientButton. Vous pouvez créer un LoginButton qui hérite de GradientButton, mais qui modifie ses couleurs lors des interactions, comme lorsqu'il est enfoncé.
- 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
styleStateen fournissantinteractionSource. Assurez-vous que l'état activé destyleStatereflète la valeur du paramètre activé fourni. - Attribuez le
interactionSourceaux modificateursfocusableetclickable. Enfin, appliquezstyleStateau 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 = 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, ) }
Vous pouvez désormais utiliser l'état interactionSource pour modifier le style à l'aide des 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 changements d'état des styles sont fournis avec une compatibilité d'animation intégrée. Vous pouvez encapsuler la nouvelle propriété dans n'importe quel bloc de changement d'état avec animate pour ajouter automatiquement des animations entre différents états. Cela est semblable aux API animate*AsState. L'exemple suivant anime borderColor du noir au bleu lorsque l'état passe à "focused" (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 boîte 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)) }
Personnaliser le style d'état avec StyleState
Selon votre cas d'utilisation composable, vous pouvez avoir différents styles qui sont soutenus par des états personnalisés. Par exemple, si vous avez une application multimédia, vous pouvez souhaiter avoir un style différent pour les boutons de votre composable MediaPlayer en fonction de l'état de lecture du lecteur. Pour créer et utiliser votre propre état personnalisé, procédez comme suit :
- Définir une clé personnalisée
- Créer l'extension
StyleState - Lien vers 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 se lance, le lecteur multimédia est dans l'état Stopped. Il est donc initialisé de cette manière :
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 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 }) }
Lien vers un état personnalisé
Définissez styleState dans votre composable et définissez 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) }