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 = 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 상태를 사용하여 스타일 블록 내의 pressed, focused, 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를 사용하여 상태 변경 블록 내에 새 속성을 래핑하여 서로 다른 상태 간에 애니메이션을 자동으로 추가할 수 있습니다. 이는 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) }