API стилей предлагает декларативный и упрощенный подход к управлению изменениями пользовательского интерфейса во время таких состояний взаимодействия, как hovered , focused и pressed . С помощью этого API вы можете значительно сократить объем шаблонного кода, обычно необходимого при использовании модификаторов.
Для обеспечения реактивного стилизации StyleState выступает в качестве стабильного интерфейса только для чтения, отслеживающего активное состояние элемента (например, его статус: включен, нажат или сфокусирован). Внутри StyleScope вы можете получить доступ к этому состоянию через свойство state , чтобы реализовать условную логику непосредственно в определениях стилей.
Взаимодействие на основе состояний: наведение курсора, фокусировка, нажатие, выбор, включение, переключение
Стили обладают встроенной поддержкой распространенных взаимодействий:
- Нажатый
- Наведенный
- Избранные
- Включено
- Переключено
Также возможна поддержка пользовательских состояний. Дополнительную информацию см. в разделе « Настройка пользовательских состояний с помощью StyleState» .
Обработка состояний взаимодействия с помощью параметров стиля.
Следующий пример демонстрирует изменение цвета background и borderColor в зависимости от состояния взаимодействия, в частности, переключение на фиолетовый цвет при наведении курсора и на синий при фокусировке:
@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) }) } ) }
Вы также можете создавать вложенные определения состояния. Например, вы можете определить определенный стиль для случаев, когда кнопка одновременно нажата и наведена на нее курсор:
@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) }) } ) }
Пользовательские элементы композиции с помощью Modifier.styleable
При создании собственных styleable компонентов необходимо связать interactionSource с styleState . Затем передайте это состояние в Modifier.styleable , чтобы использовать его.
Рассмотрим сценарий, в котором ваша система дизайна включает GradientButton . Возможно, вы захотите создать LoginButton , который наследует от GradientButton , но меняет свой цвет при взаимодействии, например, при нажатии.
- Чтобы включить обновление стиля
interactionSource, добавьтеinteractionSourceв качестве параметра в ваш составной объект. Используйте предоставленный параметр или, если он не указан, инициализируйте новыйMutableInteractionSource. - Инициализируйте
styleStateуказавinteractionSource. Убедитесь, что статус enabledstyleStateсоответствует значению предоставленного параметра enabled. - Присвойте свойство
interactionSourceмодификаторамfocusableиclickable. Наконец, применитеstyleStateк параметруstyleableмодификатора.
@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, ) }
Теперь вы можете использовать состояние interactionSource для управления изменениями стиля параметров press, focus и hovered внутри блока стиля:
@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 .Изменения стиля анимации
Изменения состояния стилей поддерживают встроенную анимацию. Вы можете обернуть новое свойство в любой блок изменения состояния с помощью animate , чтобы автоматически добавлять анимацию между различными состояниями. Это похоже на API animate*AsState . В следующем примере анимируется изменение borderColor с черного на синий при переходе в состояние "фокус":
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)) { } }
API animate принимает ` animationSpec для изменения длительности или формы кривой анимации. В следующем примере размер коробки анимируется с помощью параметра 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)) }
Настройка стиля состояний с помощью StyleState
В зависимости от сценария использования вашего составного объекта, у вас могут быть разные стили, основанные на пользовательских состояниях. Например, если у вас медиаприложение, вам может потребоваться разный стиль для кнопок в вашем составном объекте MediaPlayer в зависимости от состояния воспроизведения проигрывателя. Выполните следующие шаги, чтобы создать и использовать собственное пользовательское состояние:
- Определить пользовательский ключ
- Создайте расширение
StyleState - Ссылка на пользовательское состояние
Определить пользовательский ключ
Чтобы создать собственный стиль, основанный на состоянии, сначала создайте StyleStateKey и передайте в него значение состояния по умолчанию. При запуске приложения медиаплеер находится в состоянии Stopped , поэтому он инициализируется следующим образом:
enum class PlayerState { Stopped, Playing, Paused } val playerStateKey = StyleStateKey(PlayerState.Stopped)
Создайте функции расширения StyleState.
Определите функцию расширения в StyleState для запроса текущего playState . Затем создайте функции расширения в StyleScope с вашими пользовательскими состояниями, передавая в них playStateKey , лямбда-функцию с конкретным состоянием и стиль.
// 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 }) }
Ссылка на пользовательское состояние
Define the styleState in your composable and set the styleState.playState equal to incoming state. Pass styleState into the styleable function on the 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)) { ///.. } }
Внутри лямбда- 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) }
Ниже приведён полный фрагмент кода для этого примера:
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) }