Die Styles API bietet einen deklarativen und optimierten Ansatz zum Verwalten von Änderungen an der Benutzeroberfläche während Interaktionsstatus wie hovered, focused und pressed. Mit dieser API können Sie den Boilerplate-Code, der normalerweise bei der Verwendung von Modifizierern erforderlich ist, erheblich reduzieren.
Um reaktive Formatierung zu ermöglichen, fungiert StyleState als stabile, schreibgeschützte Schnittstelle, die den aktiven Status eines Elements (z. B. den Status „Aktiviert“, „Gedrückt“ oder „Fokus“) verfolgt. Innerhalb eines StyleScope können Sie über die state-Eigenschaft darauf zugreifen, um bedingte Logik direkt in Ihre Stildefinitionen einzufügen.
Zustandsbasierte Interaktion: Mauszeiger darauf bewegt, fokussiert, gedrückt, ausgewählt, aktiviert, umgeschaltet
Stile unterstützen standardmäßig häufige Interaktionen:
- Gedrückt
- Hovered
- Ausgewählt
- Aktiviert
- Ein-/Aus-Button
Es ist auch möglich, benutzerdefinierte Status zu unterstützen. Weitere Informationen finden Sie im Abschnitt Benutzerdefinierte Statusformatierung mit StyleState.
Interaktionsstatus mit Stilparametern verarbeiten
Im folgenden Beispiel wird gezeigt, wie die background und borderColor in Reaktion auf Interaktionsstatus geändert werden. Genauer gesagt wird die Farbe zu Lila geändert, wenn der Mauszeiger darauf bewegt wird, und zu Blau, wenn der Fokus darauf liegt:
@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) }) } ) }
Sie können auch verschachtelte Zustandsdefinitionen erstellen. Sie können beispielsweise einen bestimmten Stil definieren, der angewendet wird, wenn eine Schaltfläche gleichzeitig gedrückt und mit dem Mauszeiger darauf gezeigt wird:
@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) }) } ) }
Benutzerdefinierte Composables mit Modifier.styleable
Wenn Sie eigene styleable-Komponenten erstellen, müssen Sie ein interactionSource mit einem styleState verbinden. Übergeben Sie diesen Status dann an Modifier.styleable, um ihn zu verwenden.
Stellen Sie sich ein Szenario vor, in dem Ihr Designsystem eine GradientButton enthält. Sie können ein LoginButton erstellen, das von GradientButton abgeleitet wird, aber seine Farben bei Interaktionen wie dem Drücken ändert.
- Wenn Sie Stilaktualisierungen für
interactionSourceaktivieren möchten, fügen Sie eininteractionSourceals Parameter in Ihre Composable-Funktion ein. Verwenden Sie den bereitgestellten Parameter oder initialisieren Sie eine neueMutableInteractionSource, falls keiner angegeben ist. - Initialisieren Sie
styleState, indem Sie dieinteractionSourceangeben. Der aktivierte Status vonstyleStatemuss dem Wert des bereitgestellten Parameters „enabled“ entsprechen. - Weisen Sie
interactionSourceden Modifikatorenfocusableundclickablezu. Wenden Sie schließlich diestyleStateauf den Parameterstyleabledes Modifikators an.
@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, ) }
Sie können den Status interactionSource jetzt verwenden, um Stiländerungen mit den Optionen „Gedrückt“, „Fokus“ und „Mauszeiger darauf“ im Stilblock zu steuern:
@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.Stiländerungen animieren
Statusänderungen von Stilen werden mit integrierter Animationsunterstützung ausgeliefert. Sie können die neue Eigenschaft in einen beliebigen Block für Zustandsänderungen mit animate einfügen, um automatisch Animationen zwischen verschiedenen Zuständen hinzuzufügen. Dies ähnelt den animate*AsState-APIs. Im folgenden Beispiel wird die borderColor von Schwarz zu Blau animiert, wenn sich der Status in „Fokus“ ändert:
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)) { } }
Die animate API akzeptiert einen animationSpec, um die Dauer oder Form der Animationskurve zu ändern. Im folgenden Beispiel wird die Größe des Felds mit einer spring-Spezifikation animiert:
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)) }
Benutzerdefiniertes Styling von Status mit StyleState
Je nach Ihrem zusammensetzbaren Anwendungsfall haben Sie möglicherweise verschiedene Stile, die von benutzerdefinierten Status unterstützt werden. Wenn Sie beispielsweise eine Media-App haben, möchten Sie möglicherweise je nach Wiedergabestatus des Players unterschiedliche Formatierungen für die Schaltflächen in Ihrem MediaPlayer-Composable verwenden. So erstellen und verwenden Sie einen eigenen benutzerdefinierten Status:
- Benutzerdefinierten Schlüssel definieren
- Erstellen Sie die Erweiterung
StyleState. - Link zum benutzerdefinierten Status
Benutzerdefinierten Schlüssel definieren
Wenn Sie einen benutzerdefinierten zustandsbasierten Stil erstellen möchten, müssen Sie zuerst ein StyleStateKey erstellen und den Standardstatuswert übergeben. Wenn die App gestartet wird, befindet sich der Media Player im Status Stopped. Er wird also so initialisiert:
enum class PlayerState { Stopped, Playing, Paused } val playerStateKey = StyleStateKey(PlayerState.Stopped)
StyleState-Erweiterungsfunktionen erstellen
Definieren Sie eine Erweiterungsfunktion für StyleState, um die aktuelle playState abzufragen.
Erstellen Sie dann Erweiterungsfunktionen für StyleScope, indem Sie Ihre benutzerdefinierten Status übergeben. Dazu verwenden Sie playStateKey, eine Lambda-Funktion mit dem jeweiligen Status und dem Stil.
// 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 zum benutzerdefinierten Status
Definieren Sie styleState in Ihrem Composable und legen Sie styleState.playState auf den eingehenden Status fest. Übergeben Sie styleState an die Funktion styleable für den Modifier.
@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)) { ///.. } }
Innerhalb des style-Lambdas können Sie mithilfe der zuvor definierten Erweiterungsfunktionen statusbasiertes Styling für benutzerdefinierte Status anwenden.
@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) }
Der folgende Code ist das vollständige Snippet für dieses Beispiel:
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) }