Die Styles API bietet einen deklarativen und optimierten Ansatz zum Verwalten von UI-Änderungen während Interaktionsstatus wie hovered, focused und pressed. Mit dieser API können Sie den Boilerplate-Code erheblich reduzieren, der normalerweise bei der Verwendung von Modifikatoren erforderlich ist.
Zur Erleichterung des reaktiven Stylings dient StyleState als stabile, schreibgeschützte Schnittstelle, die den aktiven Status eines Elements verfolgt (z. B. den Status „Aktiviert“, „Gedrückt“ oder „Fokussiert“). In einem StyleScope können Sie über die Eigenschaft state darauf zugreifen, um bedingte Logik direkt in Ihren Stildefinitionen zu implementieren.
Statusbasierte Interaktion: „Hovered“, „Focused“, „Pressed“, „Selected“, „Enabled“, „Toggled“
Stile bieten integrierte Unterstützung für häufige Interaktionen:
- Gedrückt
- Mauszeiger über bewegt
- Ausgewählt
- Aktiviert
- Festgelegt
Es ist auch möglich, benutzerdefinierte Status zu unterstützen. Weitere Informationen finden Sie im Abschnitt Benutzerdefinierte Statusstile mit StyleState.
Interaktionsstatus mit Stilparametern verarbeiten
Im folgenden Beispiel wird gezeigt, wie background und borderColor als Reaktion auf Interaktionsstatus geändert werden. Insbesondere wird zu Lila gewechselt, wenn der Mauszeiger darüber 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 Statusdefinitionen erstellen. Sie können beispielsweise einen bestimmten Stil definieren, wenn eine Schaltfläche gleichzeitig gedrückt und der Mauszeiger darüber bewegt 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 zusammensetzbare Elemente mit Modifier.styleable
Wenn Sie eigene styleable-Komponenten erstellen, müssen Sie eine interactionSource mit einem styleState verbinden. Übergeben Sie diesen Status dann an Modifier.styleable, um ihn zu verwenden.
Angenommen, Ihr Designsystem enthält eine GradientButton. Möglicherweise möchten Sie eine LoginButton erstellen, die von GradientButton erbt, aber ihre Farben bei Interaktionen wie dem Drücken ändert.
- Wenn Sie Stilaktualisierungen für
interactionSourceaktivieren möchten, fügen Sie eineinteractionSourceals Parameter in Ihr zusammensetzbares Element ein. Verwenden Sie den angegebenen Parameter oder initialisieren Sie eine neueMutableInteractionSource, falls keine angegeben ist. - Initialisieren Sie
styleState, indem SieinteractionSourceangeben. Der aktivierte Status vonstyleStatemuss den Wert des angegebenen aktivierten Parameters widerspiegeln. - Weisen Sie
interactionSourceden Modifikatorenfocusableundclickablezu. Wenden Sie schließlichstyleStateauf 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 = rememberUpdatedStyleState(interactionSource) { it.isEnabled = enabled } Row( modifier = modifier .clickable( onClick = onClick, enabled = enabled, interactionSource = interactionSource, indication = null, ) .styleable(styleState, baseGradientButtonStyle then style), content = content, ) }
Sie können jetzt den Status interactionSource verwenden, um Stiländerungen mit den Optionen „Pressed“, „Focused“ und „Hovered“ 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 ändernStiländerungen animieren
Statusänderungen von Stilen bieten integrierte Unterstützung für Animationen. Sie können die neue Eigenschaft in einen beliebigen Statusänderungsblock mit animate einschließen, um automatisch Animationen zwischen verschiedenen Status hinzuzufügen. Dies ähnelt den animate*AsState-APIs. Im folgenden Beispiel wird borderColor von Schwarz zu Blau animiert, wenn sich der Status in „Focused“ ä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 eine 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)) }
Benutzerdefinierte Statusstile mit StyleState
Je nach Anwendungsfall für zusammensetzbare Elemente können Sie unterschiedliche Stile haben, die von benutzerdefinierten Status unterstützt werden. Wenn Sie beispielsweise eine Media-App haben, möchten Sie möglicherweise unterschiedliche Stile für die Schaltflächen in Ihrem zusammensetzbaren Element MediaPlayer haben, je nach Wiedergabestatus des Players. Führen Sie die folgenden Schritte aus, um einen eigenen benutzerdefinierten Status zu erstellen und zu verwenden:
- Benutzerdefinierten Schlüssel definieren
StyleState-Erweiterung erstellen- Mit benutzerdefiniertem Status verknüpfen
Benutzerdefinierten Schlüssel definieren
Wenn Sie einen benutzerdefinierten statusbasierten Stil erstellen möchten, erstellen Sie zuerst einen
StyleStateKey und übergeben Sie den Standardstatuswert. Beim Starten der App 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 den aktuellen playState abzufragen.
Erstellen Sie dann Erweiterungsfunktionen für StyleScope mit Ihren benutzerdefinierten Status, indem Sie playStateKey, ein Lambda mit dem spezifischen Status und den Stil übergeben.
// 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 }) }
Mit benutzerdefiniertem Status verknüpfen
Definieren Sie das styleState in Ihrem zusammensetzbaren Element und setzen Sie das styleState.playState
gleich dem eingehenden Status. Übergeben Sie styleState an die Funktion styleable für den Modifikator.
@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)) { ///.. } }
Im style-Lambda können Sie statusbasiertes Styling für benutzerdefinierte Status mithilfe der zuvor definierten Erweiterungsfunktionen 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) }