Interfejs Styles API oferuje deklaratywne i uproszczone podejście do zarządzania zmianami interfejsu podczas interakcji, takich jak hovered (najechano), focused (skupiono) i pressed (naciśnięto). Dzięki temu interfejsowi API możesz znacznie zmniejszyć ilość powtarzalnego kodu, który jest zwykle wymagany podczas korzystania z modyfikatorów.
Aby ułatwić stylizację reaktywną, StyleState działa jako stabilny interfejs tylko do odczytu, który śledzi aktywny stan elementu (np. jego stan włączony, naciśnięty lub skupiony). W StyleScope możesz uzyskać do niego dostęp za pomocą właściwości state, aby zaimplementować logikę warunkową bezpośrednio w definicjach stylu.
Interakcja oparta na stanie: najechano, skupiono, naciśnięto, wybrano, włączono, przełączono
Style mają wbudowaną obsługę typowych interakcji:
- Naciśnięto
- Najechano
- Wybrano
- Włączono
- Przełączono
Możesz też obsługiwać stany niestandardowe. Więcej informacji znajdziesz w sekcji Stylizacja stanu niestandardowego za pomocą StyleState.
Obsługa stanów interakcji za pomocą parametrów stylu
Poniższy przykład pokazuje, jak modyfikować background (tło) i borderColor (kolor obramowania) w odpowiedzi na stany interakcji, w szczególności przełączanie na kolor fioletowy po najechaniu i niebieski po skupieniu:
@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) }) } ) }
Możesz też tworzyć zagnieżdżone definicje stanu. Możesz na przykład zdefiniować konkretny styl, gdy przycisk jest jednocześnie naciśnięty i najechany:
@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) }) } ) }
Niestandardowe elementy kompozycyjne z Modifier.styleable
Podczas tworzenia własnych komponentów styleable musisz połączyć interactionSource z styleState. Następnie przekaż ten stan do Modifier.styleable, aby go użyć.
Rozważmy sytuację, w której system projektowania zawiera GradientButton. Możesz utworzyć LoginButton, który dziedziczy po GradientButton, ale zmienia kolory podczas interakcji, np. po naciśnięciu.
- Aby włączyć aktualizacje stylu
interactionSource, dodajinteractionSourcejako parametr w elemencie kompozycyjnym. Użyj podanego parametru lub, jeśli nie został podany, zainicjuj nowyMutableInteractionSource. - Zainicjuj
styleState, podającinteractionSource. Upewnij się, że stan włączonystyleStateodzwierciedla wartość podanego parametru włączonego. - Przypisz
interactionSourcedo modyfikatorówfocusableiclickable. Na koniec zastosujstyleStatedo parametrustyleablemodyfikatora.
@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, ) }
Teraz możesz użyć stanu interactionSource, aby sterować modyfikacjami stylu za pomocą opcji naciśnięto, skupiono i najechano w bloku stylu:
@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.Animowanie zmian stylu
Zmiany stanu stylu mają wbudowaną obsługę animacji. Aby automatycznie dodawać animacje między różnymi stanami, możesz opakować nową właściwość w dowolnym bloku zmiany stanu za pomocą animate. Jest to podobne do interfejsów API animate*AsState. Poniższy przykład animuje borderColor z czarnego na niebieski, gdy stan zmieni się na skupiony:
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)) { } }
Interfejs API animate akceptuje animationSpec, aby zmienić czas trwania lub kształt krzywej animacji. Poniższy przykład animuje rozmiar pola za pomocą specyfikacji 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)) }
Stylizacja stanu niestandardowego za pomocą StyleState
W zależności od przypadku użycia elementu kompozycyjnego możesz mieć różne style, które są oparte na stanach niestandardowych. Jeśli na przykład masz aplikację do multimediów, możesz mieć różne style przycisków w elemencie kompozycyjnym MediaPlayer w zależności od stanu odtwarzania odtwarzacza. Aby utworzyć i używać własnego stanu niestandardowego, wykonaj te czynności:
- Zdefiniuj klucz niestandardowy
- Utwórz rozszerzenie
StyleState - Połącz ze stanem niestandardowym
Zdefiniuj klucz niestandardowy
Aby utworzyć styl oparty na stanie niestandardowym, najpierw utwórz
StyleStateKey i przekaż domyślną wartość stanu. Po uruchomieniu aplikacji odtwarzacz multimediów jest w stanie Stopped (Zatrzymany), więc jest inicjowany w ten sposób:
enum class PlayerState { Stopped, Playing, Paused } val playerStateKey = StyleStateKey(PlayerState.Stopped)
Tworzenie funkcji rozszerzenia StyleState
Zdefiniuj funkcję rozszerzenia w StyleState, aby wysyłać zapytania o bieżący playState.
Następnie utwórz funkcje rozszerzenia w StyleScope ze stanami niestandardowymi, przekazując playStateKey, lambdę z konkretnym stanem i stylem.
// 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 }) }
Połącz ze stanem niestandardowym
Zdefiniuj styleState w elemencie kompozycyjnym i ustaw styleState.playState
na stan przychodzący. Przekaż styleState do funkcji styleable w modyfikatorze.
@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)) { ///.. } }
W lambdzie style możesz zastosować stylizację opartą na stanie w przypadku stanów niestandardowych, używając wcześniej zdefiniowanych funkcji rozszerzenia.
@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) }
Poniżej znajdziesz pełny fragment kodu tego przykładu:
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) }