
한 텍스트 컴포저블 내에 각각 다른 스타일을 적용하려면 buildAnnotatedString과 withStyle, append를 사용한다.
텍스트의 text 부분에 buildAnnotatedString으로 특수한 스타일을 적용하고 싶은 부분을 withStyle+append로, 기본 스타일을 적용할 부분은 그냥 append로 설정한다.
Text(
text = buildAnnotatedString {
// withStyle로 특수한 스타일을 추가로 적용한다.
withStyle(SpanStyle(color = selected.color)) { append("포인트 컬러") }
// withStyle로 감싸지 않으면 Text에 적용한 스타일을 그대로 적용한다.
append("를 선택하세요")
},
style = MaterialTheme.typography.headlineSmall.copy(
fontWeight = FontWeight.Bold
),
color = Color.White
)
withStyle로 감싸지 않은 append 텍스트는 Text 컴포저블에서 설정한 style을, withStyle로 감싼 텍스트는 Text 컴포저블에서 설정한 style에 SpanStyle로 지정한 부분만 바뀐다.

SpanStyle을 통해 다음과 같이 여러 스타일을 지정할 수 있다.
public constructor SpanStyle(
color: Color = Color.Unspecified,
fontSize: TextUnit = TextUnit.Unspecified,
fontWeight: FontWeight? = null,
fontStyle: FontStyle? = null,
fontSynthesis: FontSynthesis? = null,
fontFamily: FontFamily? = null,
fontFeatureSettings: String? = null,
letterSpacing: TextUnit = TextUnit.Unspecified,
baselineShift: BaselineShift? = null,
textGeometricTransform: TextGeometricTransform? = null,
localeList: LocaleList? = null,
background: Color = Color.Unspecified,
textDecoration: TextDecoration? = null,
shadow: Shadow? = null,
platformStyle: PlatformSpanStyle? = null,
drawStyle: DrawStyle? = null
)
포인트 컬러를 선택할 때마다 글씨 색상도 바뀌는데, 끊기듯 확 변하는 게 어색해보여서 전환 애니메이션을 추가했다.
// 선택된 컬러 변경 시 0.5초 동안 부드럽게 색상 전환
val animatedColor by animateColorAsState(
targetValue = selected.color,
animationSpec = tween(durationMillis = 500),
label = "colorAnimation"
)
포인트 컬러로 강조된 텍스트의 color를 animatedColor로 바꾸면 애니메이션이 적용된다.
...
withStyle(SpanStyle(color = animatedColor)) {
...

// 색상 데이터 클래스
enum class AccentColor(val color: Color) {
Red(Color(0xFFEF5350)),
Blue(Color(0xFF42A5F5)),
Green(Color(0xFF66BB6A)),
Yellow(Color(0xFFFFCA28)),
Purple(Color(0xFFAB47BC))
}
// 포인트 색상 선택 컴포저블
@Composable
fun AccentColorPicker() {
val colorScrollState = rememberScrollState()
var selected by remember { mutableStateOf(AccentColor.Blue) }
// 색상 변경 애니메이션
val animatedColor by animateColorAsState(
targetValue = selected.color,
animationSpec = tween(durationMillis = 500),
label = "colorAnimation"
)
Column(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
) {
Text(
text = buildAnnotatedString {
withStyle(SpanStyle(color = selected.color)) {
append("포인트 컬러")
}
append("를 선택하세요")
},
style = MaterialTheme.typography.headlineSmall.copy(
fontWeight = FontWeight.Bold
),
color = Color.White
)
Spacer(Modifier.height(32.dp))
// 색상 선택
Row(
horizontalArrangement = Arrangement.spacedBy(12.dp),
modifier = Modifier.horizontalScroll(colorScrollState)
) {
AccentColor.entries.forEach { accent ->
val isSelected = accent == selected
Box(
modifier = Modifier
.size(44.dp)
.clip(CircleShape)
.background(accent.color)
.then(
if (isSelected)
Modifier.border(2.dp, Color.White, CircleShape)
else Modifier
)
.clickable { selected = accent }
)
}
}
}
}