기존의 xml 베이스의 레이아웃을 Jetpack compose로 전환하면서 특정 부분의 텍스트에 스타일과 클릭 이벤트를 줘야 하는 Spannable 처리가 필요했다.
Spannable 처리를 통해 부분적으로 string 스타일을 바꿀 수 있을 뿐더러, 클릭시 url 오픈 등의 가이드 액션도 적용할 수 있다.
이를 위해서 사용한 API는 AnnotatedString이다.
누군가는 string을 각각 append하는 방식으로 spannable 처리를 하기도 했는데, 다국어 및 리소스 관리에서 불편함이 많을 것 같아서,
일단 기본 문장을 넣어놓고, spannable 처리를 하고자 하는 단어들을 별도로 선별해 스타일 및 클릭 이벤트를 처리하도록 적용했다.
1. AnnotatedString을 만드는 Composable 함수
AnnotatedString을 만드는데 다수의 코드가 필요하기 때문에 별도 함수로 추출해 쓰기로 했다.
@Composable
fun GetAnnotatedTerms(): AnnotatedString {
// spannable을 적용할 단어 및 단어별 가이드하고자 하는 scheme 선언
val terms = stringResource(id = R.string.terms_of_service)
val privacyPolicy = stringResource(id = R.string.privacy_policy)
val termsUrl = stringResource(id = R.string.terms_of_service_url)
val privacyPolicyUrl = stringResource(id = R.string.privacy_policy_url)
// 어노테이션을 적용할 원본 string
val fullString = stringResource(id = R.string.login_terms_guide)
// 원본 string으로부터 따온 spannable 대상 단어의 시작 인덱스와 length
val (termsStartIndex, termsLength) = fullString.indexOf(terms) to terms.length
val (privacyStartIndex, privacyLength) = fullString.indexOf(privacyPolicy) to privacyPolicy.length
return buildAnnotatedString {
// buildAnnotatedString 블럭에 원본 string을 우선 붙인다.
append(fullString)
// 먼저 따온 인덱스 값을 이용해 각 spannable 대상에 필요한 스타일을 적용한다.
addStyle(
style = SpanStyle(textDecoration = TextDecoration.Underline),
start = termsStartIndex,
end = termsStartIndex + termsLength
)
addStyle(
style = SpanStyle(textDecoration = TextDecoration.Underline),
start = privacyStartIndex,
end = privacyStartIndex + privacyLength
)
// 각 spannable 대상에 annotation 값을 태그와 함께 선언한다.
// 이는 스타일과 별개로 클릭 이벤트가 발생했을 때 처리를 위해 필요함.
addStringAnnotation(
tag = terms,
annotation = termsUrl,
start = termsStartIndex,
end = termsStartIndex + termsLength
)
addStringAnnotation(
tag = privacyPolicy,
annotation = privacyPolicyUrl,
start = privacyStartIndex,
end = privacyStartIndex + privacyLength
)
// 빌더 패턴의 함수이기 때문에 toAnnotatedString으로 AnnotatedString을 build해 마무리한다.
toAnnotatedString()
}
}
2. AnnotatedString을 사용하는 ClickableText 사용하기
// 해당 프로젝트에서는 onClick 이벤트를 최상위 composable까지 전달하기 위해 별도 listener를 사용한다.
val actionListener: MyActionListener = MyActionListener()
ClickableText(text = annotatedString,
modifier = Modifier
.fillMaxWidth(),
onClick = { offset ->
annotatedString.getStringAnnotations(offset, offset)
.firstOrNull()?.let { span ->
// span으로부터 tag와 annotation을 읽을 수 있기 때문에,
// 최종으로 이벤트를 수신하는 대상이 이벤트의 종류와 수행해야 할 명령을 손쉽게 처리가 가능하다.
actionListener.onClick(AnnotationClicked(tag = span.tag, annotation = span.item))
}
})
관련 처리를 할 때 xml 베이스의 레이아웃은 좀 더 손이 가고 지저분한 코드가 만들어졌던 것 같은데, 새삼스럽지만 다시금 Jetpack Compose의 시대에 박수를 👏
'ANDROID > Jetpack' 카테고리의 다른 글
[Android/Jetpack Compose] 컴포즈에서 터치 이벤트를 처리할 때의 유의점 (0) | 2023.09.26 |
---|---|
[Jetpack/Compose] Compose 핵심 정리 (0) | 2022.04.19 |
[안드로이드] 안드로이드 스튜디오 3.6 업데이트, ViewBinding 사용해보기 (0) | 2020.03.01 |
[안드로이드 | Jetpack] 안드로이드 Navigation 구현하기 (0) | 2019.12.15 |