728x90
반응형
이번 공부 노트는 Android-Compose에서 텍스트 내용 중 하이퍼링크를 적용하는 법을 알아보겠다.
아래 코드는 스택오버플로우에서 구했는데 정확히 그 링크를 찾지 못하였다..ㅜㅜ
# 적용 코드
import android.os.Build
import android.text.SpannableString
import android.text.style.URLSpan
import android.text.util.Linkify
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.text.selection.SelectionContainer
import androidx.compose.material.LocalTextStyle
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.text.*
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.TextUnit
//컴포즈에서 하이퍼링크 적용 함수
@Composable
fun LinkifyText(
text: String,
modifier: Modifier = Modifier,
linkColor: Color = Color.Blue,
linkEntire: Boolean = false,
color: Color = Color.Unspecified,
fontSize: TextUnit = TextUnit.Unspecified,
fontStyle: FontStyle? = null,
fontWeight: FontWeight? = null,
fontFamily: FontFamily? = null,
letterSpacing: TextUnit = TextUnit.Unspecified,
textDecoration: TextDecoration? = null,
textAlign: TextAlign? = null,
lineHeight: TextUnit = TextUnit.Unspecified,
overflow: TextOverflow = TextOverflow.Clip,
softWrap: Boolean = true,
maxLines: Int = Int.MAX_VALUE,
onTextLayout: (TextLayoutResult) -> Unit = {},
style: TextStyle = LocalTextStyle.current,
clickable: Boolean = true,
onClickLink: ((linkText: String) -> Unit)? = null
) {
val uriHandler = LocalUriHandler.current
val linkInfos =
if (linkEntire) listOf(LinkInfo(text, 0, text.length)) else SpannableStr.getLinkInfos(text)
val annotatedString = buildAnnotatedString {
append(text)
linkInfos.forEach {
addStyle(
style = SpanStyle(
color = linkColor,
textDecoration = TextDecoration.Underline
),
start = it.start,
end = it.end
)
addStringAnnotation(
tag = "tag",
annotation = it.url,
start = it.start,
end = it.end
)
}
}
if (clickable) {
ClickableText(
text = annotatedString,
modifier = modifier,
color = color,
fontSize = fontSize,
fontStyle = fontStyle,
fontWeight = fontWeight,
fontFamily = fontFamily,
letterSpacing = letterSpacing,
textDecoration = textDecoration,
textAlign = textAlign,
lineHeight = lineHeight,
overflow = overflow,
softWrap = softWrap,
maxLines = maxLines,
onTextLayout = onTextLayout,
style = style,
onClick = { offset ->
annotatedString.getStringAnnotations(
start = offset,
end = offset,
).firstOrNull()?.let { result ->
if (linkEntire) {
onClickLink?.invoke(annotatedString.substring(result.start, result.end))
} else {
uriHandler.openUri(result.item)
onClickLink?.invoke(annotatedString.substring(result.start, result.end))
}
}
}
)
} else {
Text(
text = annotatedString,
modifier = modifier,
color = color,
fontSize = fontSize,
fontStyle = fontStyle,
fontWeight = fontWeight,
fontFamily = fontFamily,
letterSpacing = letterSpacing,
textDecoration = textDecoration,
textAlign = textAlign,
lineHeight = lineHeight,
overflow = overflow,
softWrap = softWrap,
maxLines = maxLines,
onTextLayout = onTextLayout,
style = style
)
}
}
@Composable
private fun ClickableText(
text: AnnotatedString,
modifier: Modifier = Modifier,
color: Color = Color.Unspecified,
fontSize: TextUnit = TextUnit.Unspecified,
fontStyle: FontStyle? = null,
fontWeight: FontWeight? = null,
fontFamily: FontFamily? = null,
letterSpacing: TextUnit = TextUnit.Unspecified,
textDecoration: TextDecoration? = null,
textAlign: TextAlign? = null,
lineHeight: TextUnit = TextUnit.Unspecified,
overflow: TextOverflow = TextOverflow.Clip,
softWrap: Boolean = true,
maxLines: Int = Int.MAX_VALUE,
onTextLayout: (TextLayoutResult) -> Unit = {},
style: TextStyle = LocalTextStyle.current,
onClick: (Int) -> Unit
) {
val layoutResult = remember { mutableStateOf<TextLayoutResult?>(null) }
val pressIndicator = Modifier.pointerInput(onClick) {
detectTapGestures { pos ->
layoutResult.value?.let { layoutResult ->
onClick(layoutResult.getOffsetForPosition(pos))
}
}
}
SelectionContainer {
Text(
text = text,
modifier = modifier.then(pressIndicator),
color = color,
fontSize = fontSize,
fontStyle = fontStyle,
fontWeight = fontWeight,
fontFamily = fontFamily,
letterSpacing = letterSpacing,
textDecoration = textDecoration,
textAlign = textAlign,
lineHeight = lineHeight,
overflow = overflow,
softWrap = softWrap,
maxLines = maxLines,
onTextLayout = {
layoutResult.value = it
onTextLayout(it)
},
style = style
)
}
}
private data class LinkInfo(
val url: String,
val start: Int,
val end: Int
)
private class SpannableStr(source: CharSequence) : SpannableString(source) {
companion object {
fun getLinkInfos(text: String): List<LinkInfo> {
val spannableStr = SpannableStr(text)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
Linkify.addLinks(spannableStr, Linkify.ALL) { str: String -> URLSpan(str) }
} else {
Linkify.addLinks(spannableStr, Linkify.ALL)
}
return spannableStr.linkInfos
}
}
private inner class Data(
val what: Any?,
val start: Int,
val end: Int
)
private val spanList = mutableListOf<Data>()
private val linkInfos: List<LinkInfo>
get() = spanList.filter { it.what is URLSpan }.map {
LinkInfo(
(it.what as URLSpan).url,
it.start,
it.end
)
}
override fun removeSpan(what: Any?) {
super.removeSpan(what)
spanList.removeAll { it.what == what }
}
override fun setSpan(what: Any?, start: Int, end: Int, flags: Int) {
super.setSpan(what, start, end, flags)
spanList.add(Data(what, start, end))
}
}
코틀린 파일을 하나 생성 해 위 파일을 복사 후 붙여 넣고 적용해 주면 완료가 된다.
# 위 컴포저블 함수를 이용한 예제 코드

@Composable
fun HyperLinkEx() {
val sampleText = stringResource(id = R.string.sample_text)
// R.string.sample_text = "이것은 하이퍼링크 텍스트 테스트 입니다.
// \n https://www.naver.com 네이버 링크 http://www.naver.com 그냥 http 링크
// \n www.naver.com www만 있는 링크\n naver.com www도 없는 링크
// \n 문자열 사이에 있는 https://www.naver.com 링크"
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
// 만들어준 LinkifyText를 통해 작성
LinkifyText(
text = sampleText,
modifier = Modifier.fillMaxWidth(),
)
}
}
보면 위에 코드와 같이 활용을 할 수 있다.
헷갈리면 아래 깃허브 링크에서 HyperLinkEx 컴포저블 함수를 보면 된다.
깃허브 링크
https://github.com/JiSeokYeom/BlogComposeEx_2
GitHub - JiSeokYeom/BlogComposeEx_2
Contribute to JiSeokYeom/BlogComposeEx_2 development by creating an account on GitHub.
github.com
반응형
'안드로이드 공부 노트 > Compose(컴포즈)' 카테고리의 다른 글
| [Android-Compose]안드로이드 컴포즈 백 버튼/ 뒤로가기 처리 (onbackpressed) (1) | 2023.07.30 |
|---|---|
| [Android-Compose] Spacer를 이용해 간단한 선 만들기 (0) | 2023.05.25 |
| [Android-Compose] 안드로이드 컴포즈 5편 예제를 통한 Scaffold 알아보기! - topBar (0) | 2022.12.18 |
| [Android-Compose] 안드로이드 컴포즈 4편 Lazy lists - LazyColumn, LazyRow (0) | 2022.11.06 |
| [Android-Compose] 안드로이드 컴포즈 3편 - Row,Column (0) | 2022.09.04 |