Interfejs Styles API oferuje deklaratywne i uproszczone podejście do zarządzania zmianami interfejsu podczas stanów interakcji, takich jak hovered, focused i pressed. Ten interfejs API pozwala znacznie ograniczyć 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 zaznaczony). 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, zaznaczono, naciśnięto, wybrano, włączono, przełączono
Style mają wbudowaną obsługę typowych interakcji:
- Naciśnięty
- Najechano
- Wybrane
- Włączono
- Przełączono
Możesz też obsługiwać stany niestandardowe. Więcej informacji znajdziesz w sekcji Stylizacja stanu niestandardowego za pomocą komponentu StyleState.
Obsługa stanów interakcji za pomocą parametrów stylu
Poniższy przykład pokazuje, jak zmodyfikować background i borderColor w odpowiedzi na stany interakcji, w szczególności zmienić kolor na fioletowy po najechaniu kursorem i na niebieski po zaznaczeniu:
@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 stanów. Możesz na przykład zdefiniować konkretny styl, który będzie stosowany, gdy przycisk jest jednocześnie naciskany 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) }) } ) }
Komponenty z możliwością dostosowania za pomocą Modifier.styleable
Podczas tworzenia własnych styleable komponentów musisz połączyć interactionSource z styleState. Następnie przekaż ten stan do funkcji
Modifier.styleable, aby go wykorzystać.
Rozważmy scenariusz, w którym system projektowania zawiera element 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, w funkcji kompozycyjnej umieść parametrinteractionSource. Użyj podanego parametru lub, jeśli nie został on podany, zainicjuj nowy obiektMutableInteractionSource. - Zainicjuj obiekt
styleState, podającinteractionSource. Upewnij się, że stan włączonystyleStateodzwierciedla wartość podanego parametru enabled. - 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 = 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, ) }
Możesz teraz używać stanu interactionSource do wprowadzania zmian w stylu za pomocą opcji naciśnięty, zaznaczony i najechany 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 stylów mają wbudowaną obsługę animacji. Nową właściwość możesz umieścić w dowolnym bloku zmiany stanu za pomocą funkcji animate, aby automatycznie dodawać animacje między różnymi stanami. Działa to podobnie jak interfejsy animate*AsState. W tym przykładzie kolor borderColor zmienia się 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. W tym przykładzie animujemy 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)) }
Dostosowywanie stylu stanu za pomocą StyleState
W zależności od przypadku użycia komponentu 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 chcieć zastosować różne style przycisków w MediaPlayer komponencie
w zależności od stanu odtwarzania odtwarzacza. Aby utworzyć i używać własnego stanu niestandardowego:
- Definiowanie klucza niestandardowego
- Tworzenie rozszerzenia
StyleState - Link do stanu niestandardowego
Definiowanie klucza niestandardowego
Aby utworzyć niestandardowy styl oparty na stanie, najpierw utwórz StyleStateKey i przekaż domyślną wartość stanu. Po uruchomieniu aplikacji odtwarzacz multimediów jest w stanie Stopped, 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 do bieżącego playState.
Następnie utwórz funkcje rozszerzenia w StyleScope z własnymi stanami przekazywanymi w playStateKey, lambdę z określonym 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 }) }
Link do stanu niestandardowego
Zdefiniuj styleState w funkcji kompozycyjnej i ustaw styleState.playState
równą przychodzącemu stanowi. 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 funkcji style lambda możesz zastosować stylizację opartą na stanie w przypadku stanów niestandardowych, używając zdefiniowanych wcześniej 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) }
Oto pełny fragment kodu w tym przykładzie:
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) }