Styles API는 hovered, focused, pressed와 같은 상호작용 상태에서 UI 변경을 관리하는 선언적이고 간소화된 접근 방식을 제공합니다. 이 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이 포함된 시나리오를 고려해 보세요. GradientButton에서 상속되지만 눌림과 같은 상호작용 중에 색상을 변경하는 LoginButton을 만들 수 있습니다.
interactionSource스타일 업데이트를 사용 설정하려면 컴포저블 내에interactionSource를 매개변수로 포함합니다. 제공된 매개변수를 사용하거나 제공되지 않은 경우 새MutableInteractionSource를 초기화합니다.interactionSource를 제공하여styleState를 초기화합니다.styleState의 사용 설정 상태가 제공된 사용 설정 매개변수의 값을 반영하는지 확인합니다.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 = rememberUpdatedStyleState(interactionSource) { it.isEnabled = enabled } Row( modifier = modifier .clickable( onClick = onClick, enabled = enabled, interactionSource = interactionSource, indication = null, ) .styleable(styleState, baseGradientButtonStyle then style), content = content, ) }
이제 interactionSource 상태를 사용하여 스타일 블록 내에서 눌림, 포커스됨, 마우스 오버 옵션으로 스타일 수정을 실행할 수 있습니다.
@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를 사용하여 상태 변경 블록 내에 새 속성을 래핑하면 여러 상태 간에 애니메이션이 자동으로 추가됩니다. 이는 animate*AsState API와 유사합니다. 다음 예에서는 상태가 포커스됨으로 변경될 때 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)) { } }
animate API는 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를 쿼리합니다.
그런 다음 playStateKey, 특정 상태가 있는 람다, 스타일을 전달하여 맞춤 상태로 StyleScope에서 확장 함수를 만듭니다.
// 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 }) }
맞춤 상태에 연결
컴포저블에서 styleState를 정의하고 styleState.playState
수신 상태와 같게 설정합니다. styleState를 수정자의 styleable 함수에 전달합니다.
@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) }