Jetpack Compose 文本超链接文本的某些部分
Jetpack Compose Text hyperlink some section of the text
如何将 hyperlink 添加到 Text 组件文本的某些部分?
使用 buildAnnotatedString
我可以将 link 部分设置为蓝色和下划线,如下图所示,但是我怎样才能将该部分也变成 link?
val annotatedLinkString = buildAnnotatedString {
val str = "Click this link to go to web site"
val startIndex = str.indexOf("link")
val endIndex = startIndex + 4
append(str)
addStyle(
style = SpanStyle(
color = Color(0xff64B5F6),
textDecoration = TextDecoration.Underline
), start = startIndex, end = endIndex
)
}
Text(
modifier = modifier
.padding(16.dp)
.fillMaxWidth(),
text = annotatedLinkString
)
我也可以获得 Spanned
但有什么方法可以将它与 Text
一起使用吗?
val str: Spanned = HtmlCompat.fromHtml(
"<a href=\"http://www.github.com\">Github</a>", HtmlCompat.FROM_HTML_MODE_LEGACY
)
How can i add hyperlink to some section of the text of Text component?
with(AnnotatedString.Builder()) {
append("link: Jetpack Compose")
// attach a string annotation that stores a URL to the text "Jetpack Compose".
addStringAnnotation(
tag = "URL",
annotation = "https://developer.android.com/jetpack/compose",
start = 6,
end = 21
)
}
tag:The tag used to distinguish annotations
annotation: The string annotation that is attached
start: The inclusive starting offset of the range
end: The exclusive end offset of the
要获得完整的答案,您可以使用 ClickableText
其中 returns 文本的位置,以及 UriHandler
在浏览器中打开 URI。
val annotatedLinkString: AnnotatedString = buildAnnotatedString {
val str = "Click this link to go to web site"
val startIndex = str.indexOf("link")
val endIndex = startIndex + 4
append(str)
addStyle(
style = SpanStyle(
color = Color(0xff64B5F6),
fontSize = 18.sp,
textDecoration = TextDecoration.Underline
), start = startIndex, end = endIndex
)
// attach a string annotation that stores a URL to the text "link"
addStringAnnotation(
tag = "URL",
annotation = "https://github.com",
start = startIndex,
end = endIndex
)
}
// UriHandler parse and opens URI inside AnnotatedString Item in Browse
val uriHandler = LocalUriHandler.current
// Clickable text returns position of text that is clicked in onClick callback
ClickableText(
modifier = modifier
.padding(16.dp)
.fillMaxWidth(),
text = annotatedLinkString,
onClick = {
annotatedLinkString
.getStringAnnotations("URL", it, it)
.firstOrNull()?.let { stringAnnotation ->
uriHandler.openUri(stringAnnotation.item)
}
}
)
标注的答案让新手一头雾水,我举个完整的例子
请不要忘记以 pop()
结束 pushStringAnnotation
val annotatedString = buildAnnotatedString {
append("By joining, you agree to the ")
pushStringAnnotation(tag = "policy", annotation = "https://google.com/policy")
withStyle(style = SpanStyle(color = MaterialTheme.colors.primary)) {
append("privacy policy")
}
pop()
append(" and ")
pushStringAnnotation(tag = "terms", annotation = "https://google.com/terms")
withStyle(style = SpanStyle(color = MaterialTheme.colors.primary)) {
append("terms of use")
}
pop()
}
ClickableText(text = annotatedString, style = MaterialTheme.typography.body1, onClick = { offset ->
annotatedString.getStringAnnotations(tag = "policy", start = offset, end = offset).firstOrNull()?.let {
Log.d("policy URL", it.item)
}
annotatedString.getStringAnnotations(tag = "terms", start = offset, end = offset).firstOrNull()?.let {
Log.d("terms URL", it.item)
}
})
最终效果
对于寻找可重复使用的复制粘贴解决方案的任何人,
创建一个新文件LinkText.kt
并复制粘贴此代码,
data class LinkTextData(
val text: String,
val tag: String? = null,
val annotation: String? = null,
val onClick: ((str: AnnotatedString.Range<String>) -> Unit)? = null,
)
@Composable
fun LinkText(
linkTextData: List<LinkTextData>,
modifier: Modifier = Modifier,
) {
val annotatedString = createAnnotatedString(linkTextData)
ClickableText(
text = annotatedString,
style = MaterialTheme.typography.body1,
onClick = { offset ->
linkTextData.forEach { annotatedStringData ->
if (annotatedStringData.tag != null && annotatedStringData.annotation != null) {
annotatedString.getStringAnnotations(
tag = annotatedStringData.tag,
start = offset,
end = offset,
).firstOrNull()?.let {
annotatedStringData.onClick?.invoke(it)
}
}
}
},
modifier = modifier,
)
}
@Composable
private fun createAnnotatedString(data: List<LinkTextData>): AnnotatedString {
return buildAnnotatedString {
data.forEach { linkTextData ->
if (linkTextData.tag != null && linkTextData.annotation != null) {
pushStringAnnotation(
tag = linkTextData.tag,
annotation = linkTextData.annotation,
)
withStyle(
style = SpanStyle(
color = MaterialTheme.colors.primary,
textDecoration = TextDecoration.Underline,
),
) {
append(linkTextData.text)
}
pop()
} else {
append(linkTextData.text)
}
}
}
}
用法
LinkText(
linkTextData = listOf(
LinkTextData(
text = "Icons made by ",
),
LinkTextData(
text = "smalllikeart",
tag = "icon_1_author",
annotation = "https://www.flaticon.com/authors/smalllikeart",
onClick = {
Log.d("Link text", "${it.tag} ${it.item}")
},
),
LinkTextData(
text = " from ",
),
LinkTextData(
text = "Flaticon",
tag = "icon_1_source",
annotation = "https://www.flaticon.com/",
onClick = {
Log.d("Link text", "${it.tag} ${it.item}")
},
)
),
modifier = Modifier
.padding(
all = 16.dp,
),
)
截图,
备注
- 我正在使用可组合项手动处理网页。如果不需要手动控制,请使用
UriHandler
或其他替代方法。
- 根据
LinkText
中的要求设置可点击和其他文本的样式。
您可以使用https://github.com/firefinchdev/linkify-text
它是一个文件,您可以直接将其复制到您的项目中。
另外,它使用Android的Linkify进行link检测,与TextView
的autoLink
相同。
如果你想让文本的某些部分可以点击,那么下面的代码就可以了。
@Composable
fun SingUpText() {
val annotatedText = buildAnnotatedString {
val grayStyle = SpanStyle(color = Color.Gray)
pushStyle(grayStyle)
append("Don't have an account? ")
pop()
pushStringAnnotation(
tag = "SignUp",
annotation = "SignUp"
)
val style = SpanStyle(color = AppColor, fontWeight = FontWeight.Bold)
pushStyle(style)
append("Sign Up")
pop()
}
ClickableText(text = annotatedText, onClick = {
annotatedText.getStringAnnotations(
tag = "SingUp",
start = it,
end = it
).firstOrNull().let { annotatedText ->
Log.d("Text_Clicked", "SingUpText:text ")
}
})
}
如果您使用硬编码字符串,这里的答案都很好,但它们对字符串资源不是很有用。下面是一些代码,可为您提供与 old-school TextView 如何与完全使用 Jetpack Compose(无互操作 API)构建的 HTML 协同工作的类似功能。此答案的 99% 归功于 comment on this issue, which I extended to use the Android String resource Annotation tag 以支持 URL。 [注意:此解决方案目前不支持 BulletSpan,因为我的用例不需要它,而且我没有花时间解决它在我扩展的解决方案中的缺失]
const val URL_ANNOTATION_KEY = "url"
/**
* Much of this class comes from
* https://issuetracker.google.com/issues/139320238#comment11
* which seeks to correct the gap in Jetpack Compose wherein HTML style tags in string resources
* are not respected.
*/
@Composable
@ReadOnlyComposable
private fun resources(): Resources {
return LocalContext.current.resources
}
fun Spanned.toHtmlWithoutParagraphs(): String {
return HtmlCompat.toHtml(this, HtmlCompat.TO_HTML_PARAGRAPH_LINES_CONSECUTIVE)
.substringAfter("<p dir=\"ltr\">").substringBeforeLast("</p>")
}
fun Resources.getText(@StringRes id: Int, vararg args: Any): CharSequence {
val escapedArgs = args.map {
if (it is Spanned) it.toHtmlWithoutParagraphs() else it
}.toTypedArray()
val resource = SpannedString(getText(id))
val htmlResource = resource.toHtmlWithoutParagraphs()
val formattedHtml = String.format(htmlResource, *escapedArgs)
return HtmlCompat.fromHtml(formattedHtml, HtmlCompat.FROM_HTML_MODE_LEGACY)
}
@Composable
fun annotatedStringResource(@StringRes id: Int, vararg formatArgs: Any): AnnotatedString {
val resources = resources()
val density = LocalDensity.current
return remember(id, formatArgs) {
val text = resources.getText(id, *formatArgs)
spannableStringToAnnotatedString(text, density)
}
}
@Composable
fun annotatedStringResource(@StringRes id: Int): AnnotatedString {
val resources = resources()
val density = LocalDensity.current
return remember(id) {
val text = resources.getText(id)
spannableStringToAnnotatedString(text, density)
}
}
private fun spannableStringToAnnotatedString(
text: CharSequence,
density: Density
): AnnotatedString {
return if (text is Spanned) {
with(density) {
buildAnnotatedString {
append((text.toString()))
text.getSpans(0, text.length, Any::class.java).forEach {
val start = text.getSpanStart(it)
val end = text.getSpanEnd(it)
when (it) {
is StyleSpan -> when (it.style) {
Typeface.NORMAL -> addStyle(
style = SpanStyle(
fontWeight = FontWeight.Normal,
fontStyle = FontStyle.Normal
),
start = start,
end = end
)
Typeface.BOLD -> addStyle(
style = SpanStyle(
fontWeight = FontWeight.Bold,
fontStyle = FontStyle.Normal
),
start = start,
end = end
)
Typeface.ITALIC -> addStyle(
style = SpanStyle(
fontWeight = FontWeight.Normal,
fontStyle = FontStyle.Italic
),
start = start,
end = end
)
Typeface.BOLD_ITALIC -> addStyle(
style = SpanStyle(
fontWeight = FontWeight.Bold,
fontStyle = FontStyle.Italic
),
start = start,
end = end
)
}
is TypefaceSpan -> addStyle(
style = SpanStyle(
fontFamily = when (it.family) {
FontFamily.SansSerif.name -> FontFamily.SansSerif
FontFamily.Serif.name -> FontFamily.Serif
FontFamily.Monospace.name -> FontFamily.Monospace
FontFamily.Cursive.name -> FontFamily.Cursive
else -> FontFamily.Default
}
),
start = start,
end = end
)
is BulletSpan -> {
Log.d("StringResources", "BulletSpan not supported yet")
addStyle(style = SpanStyle(), start = start, end = end)
}
is AbsoluteSizeSpan -> addStyle(
style = SpanStyle(fontSize = if (it.dip) it.size.dp.toSp() else it.size.toSp()),
start = start,
end = end
)
is RelativeSizeSpan -> addStyle(
style = SpanStyle(fontSize = it.sizeChange.em),
start = start,
end = end
)
is StrikethroughSpan -> addStyle(
style = SpanStyle(textDecoration = TextDecoration.LineThrough),
start = start,
end = end
)
is UnderlineSpan -> addStyle(
style = SpanStyle(textDecoration = TextDecoration.Underline),
start = start,
end = end
)
is SuperscriptSpan -> addStyle(
style = SpanStyle(baselineShift = BaselineShift.Superscript),
start = start,
end = end
)
is SubscriptSpan -> addStyle(
style = SpanStyle(baselineShift = BaselineShift.Subscript),
start = start,
end = end
)
is ForegroundColorSpan -> addStyle(
style = SpanStyle(color = Color(it.foregroundColor)),
start = start,
end = end
)
is Annotation -> {
if (it.key == URL_ANNOTATION_KEY) {
addStyle(
style = SpanStyle(color = Color.Blue),
start = start,
end = end
)
addStringAnnotation(
tag = "URL",
annotation = it.value,
start = start,
end = end
)
}
}
else -> addStyle(style = SpanStyle(), start = start, end = end)
}
}
}
}
} else {
AnnotatedString(text = text.toString())
}
}
@Composable
fun LinkableTextView(
@StringRes id: Int,
modifier: Modifier = Modifier,
style: TextStyle = MaterialTheme.typography.body1
) {
val uriHandler = LocalUriHandler.current
val annotatedString = annotatedStringResource(id)
ClickableText(
text = annotatedString,
style = style,
onClick = { offset ->
annotatedString.getStringAnnotations(
tag = "URL",
start = offset,
end = offset
).firstOrNull()?.let {
uriHandler.openUri(it.item)
}
},
modifier = modifier,
)
}
用法:
@Composable
fun MyComposableView {
LinkableTextView(
id = R.string.my_link_string
)
}
字符串资源:
<string name="my_link_string">Click this
<annotation url="https://www.whosebug.com">link</annotation>
to go to web site
</string>
还有一种“愚蠢”的方法,就是退回到使用 android.widget.TextView,它具有您正在寻找的行为:
@Composable
fun CompatHtmlTextView(@StringRes htmlStringResource: Int) {
val html = stringResource(htmlStringResource)
AndroidView(factory = { context ->
android.widget.TextView(context).apply {
text = fromHtml(html)
}
})
}
如何将 hyperlink 添加到 Text 组件文本的某些部分?
使用 buildAnnotatedString
我可以将 link 部分设置为蓝色和下划线,如下图所示,但是我怎样才能将该部分也变成 link?
val annotatedLinkString = buildAnnotatedString {
val str = "Click this link to go to web site"
val startIndex = str.indexOf("link")
val endIndex = startIndex + 4
append(str)
addStyle(
style = SpanStyle(
color = Color(0xff64B5F6),
textDecoration = TextDecoration.Underline
), start = startIndex, end = endIndex
)
}
Text(
modifier = modifier
.padding(16.dp)
.fillMaxWidth(),
text = annotatedLinkString
)
我也可以获得 Spanned
但有什么方法可以将它与 Text
一起使用吗?
val str: Spanned = HtmlCompat.fromHtml(
"<a href=\"http://www.github.com\">Github</a>", HtmlCompat.FROM_HTML_MODE_LEGACY
)
How can i add hyperlink to some section of the text of Text component?
with(AnnotatedString.Builder()) {
append("link: Jetpack Compose")
// attach a string annotation that stores a URL to the text "Jetpack Compose".
addStringAnnotation(
tag = "URL",
annotation = "https://developer.android.com/jetpack/compose",
start = 6,
end = 21
)
}
tag:The tag used to distinguish annotations
annotation: The string annotation that is attached
start: The inclusive starting offset of the range
end: The exclusive end offset of the
要获得完整的答案,您可以使用 ClickableText
其中 returns 文本的位置,以及 UriHandler
在浏览器中打开 URI。
val annotatedLinkString: AnnotatedString = buildAnnotatedString {
val str = "Click this link to go to web site"
val startIndex = str.indexOf("link")
val endIndex = startIndex + 4
append(str)
addStyle(
style = SpanStyle(
color = Color(0xff64B5F6),
fontSize = 18.sp,
textDecoration = TextDecoration.Underline
), start = startIndex, end = endIndex
)
// attach a string annotation that stores a URL to the text "link"
addStringAnnotation(
tag = "URL",
annotation = "https://github.com",
start = startIndex,
end = endIndex
)
}
// UriHandler parse and opens URI inside AnnotatedString Item in Browse
val uriHandler = LocalUriHandler.current
// Clickable text returns position of text that is clicked in onClick callback
ClickableText(
modifier = modifier
.padding(16.dp)
.fillMaxWidth(),
text = annotatedLinkString,
onClick = {
annotatedLinkString
.getStringAnnotations("URL", it, it)
.firstOrNull()?.let { stringAnnotation ->
uriHandler.openUri(stringAnnotation.item)
}
}
)
标注的答案让新手一头雾水,我举个完整的例子
请不要忘记以 pop()
pushStringAnnotation
val annotatedString = buildAnnotatedString {
append("By joining, you agree to the ")
pushStringAnnotation(tag = "policy", annotation = "https://google.com/policy")
withStyle(style = SpanStyle(color = MaterialTheme.colors.primary)) {
append("privacy policy")
}
pop()
append(" and ")
pushStringAnnotation(tag = "terms", annotation = "https://google.com/terms")
withStyle(style = SpanStyle(color = MaterialTheme.colors.primary)) {
append("terms of use")
}
pop()
}
ClickableText(text = annotatedString, style = MaterialTheme.typography.body1, onClick = { offset ->
annotatedString.getStringAnnotations(tag = "policy", start = offset, end = offset).firstOrNull()?.let {
Log.d("policy URL", it.item)
}
annotatedString.getStringAnnotations(tag = "terms", start = offset, end = offset).firstOrNull()?.let {
Log.d("terms URL", it.item)
}
})
最终效果
对于寻找可重复使用的复制粘贴解决方案的任何人,
创建一个新文件LinkText.kt
并复制粘贴此代码,
data class LinkTextData(
val text: String,
val tag: String? = null,
val annotation: String? = null,
val onClick: ((str: AnnotatedString.Range<String>) -> Unit)? = null,
)
@Composable
fun LinkText(
linkTextData: List<LinkTextData>,
modifier: Modifier = Modifier,
) {
val annotatedString = createAnnotatedString(linkTextData)
ClickableText(
text = annotatedString,
style = MaterialTheme.typography.body1,
onClick = { offset ->
linkTextData.forEach { annotatedStringData ->
if (annotatedStringData.tag != null && annotatedStringData.annotation != null) {
annotatedString.getStringAnnotations(
tag = annotatedStringData.tag,
start = offset,
end = offset,
).firstOrNull()?.let {
annotatedStringData.onClick?.invoke(it)
}
}
}
},
modifier = modifier,
)
}
@Composable
private fun createAnnotatedString(data: List<LinkTextData>): AnnotatedString {
return buildAnnotatedString {
data.forEach { linkTextData ->
if (linkTextData.tag != null && linkTextData.annotation != null) {
pushStringAnnotation(
tag = linkTextData.tag,
annotation = linkTextData.annotation,
)
withStyle(
style = SpanStyle(
color = MaterialTheme.colors.primary,
textDecoration = TextDecoration.Underline,
),
) {
append(linkTextData.text)
}
pop()
} else {
append(linkTextData.text)
}
}
}
}
用法
LinkText(
linkTextData = listOf(
LinkTextData(
text = "Icons made by ",
),
LinkTextData(
text = "smalllikeart",
tag = "icon_1_author",
annotation = "https://www.flaticon.com/authors/smalllikeart",
onClick = {
Log.d("Link text", "${it.tag} ${it.item}")
},
),
LinkTextData(
text = " from ",
),
LinkTextData(
text = "Flaticon",
tag = "icon_1_source",
annotation = "https://www.flaticon.com/",
onClick = {
Log.d("Link text", "${it.tag} ${it.item}")
},
)
),
modifier = Modifier
.padding(
all = 16.dp,
),
)
截图,
备注
- 我正在使用可组合项手动处理网页。如果不需要手动控制,请使用
UriHandler
或其他替代方法。 - 根据
LinkText
中的要求设置可点击和其他文本的样式。
您可以使用https://github.com/firefinchdev/linkify-text
它是一个文件,您可以直接将其复制到您的项目中。
另外,它使用Android的Linkify进行link检测,与TextView
的autoLink
相同。
如果你想让文本的某些部分可以点击,那么下面的代码就可以了。
@Composable
fun SingUpText() {
val annotatedText = buildAnnotatedString {
val grayStyle = SpanStyle(color = Color.Gray)
pushStyle(grayStyle)
append("Don't have an account? ")
pop()
pushStringAnnotation(
tag = "SignUp",
annotation = "SignUp"
)
val style = SpanStyle(color = AppColor, fontWeight = FontWeight.Bold)
pushStyle(style)
append("Sign Up")
pop()
}
ClickableText(text = annotatedText, onClick = {
annotatedText.getStringAnnotations(
tag = "SingUp",
start = it,
end = it
).firstOrNull().let { annotatedText ->
Log.d("Text_Clicked", "SingUpText:text ")
}
})
}
如果您使用硬编码字符串,这里的答案都很好,但它们对字符串资源不是很有用。下面是一些代码,可为您提供与 old-school TextView 如何与完全使用 Jetpack Compose(无互操作 API)构建的 HTML 协同工作的类似功能。此答案的 99% 归功于 comment on this issue, which I extended to use the Android String resource Annotation tag 以支持 URL。 [注意:此解决方案目前不支持 BulletSpan,因为我的用例不需要它,而且我没有花时间解决它在我扩展的解决方案中的缺失]
const val URL_ANNOTATION_KEY = "url"
/**
* Much of this class comes from
* https://issuetracker.google.com/issues/139320238#comment11
* which seeks to correct the gap in Jetpack Compose wherein HTML style tags in string resources
* are not respected.
*/
@Composable
@ReadOnlyComposable
private fun resources(): Resources {
return LocalContext.current.resources
}
fun Spanned.toHtmlWithoutParagraphs(): String {
return HtmlCompat.toHtml(this, HtmlCompat.TO_HTML_PARAGRAPH_LINES_CONSECUTIVE)
.substringAfter("<p dir=\"ltr\">").substringBeforeLast("</p>")
}
fun Resources.getText(@StringRes id: Int, vararg args: Any): CharSequence {
val escapedArgs = args.map {
if (it is Spanned) it.toHtmlWithoutParagraphs() else it
}.toTypedArray()
val resource = SpannedString(getText(id))
val htmlResource = resource.toHtmlWithoutParagraphs()
val formattedHtml = String.format(htmlResource, *escapedArgs)
return HtmlCompat.fromHtml(formattedHtml, HtmlCompat.FROM_HTML_MODE_LEGACY)
}
@Composable
fun annotatedStringResource(@StringRes id: Int, vararg formatArgs: Any): AnnotatedString {
val resources = resources()
val density = LocalDensity.current
return remember(id, formatArgs) {
val text = resources.getText(id, *formatArgs)
spannableStringToAnnotatedString(text, density)
}
}
@Composable
fun annotatedStringResource(@StringRes id: Int): AnnotatedString {
val resources = resources()
val density = LocalDensity.current
return remember(id) {
val text = resources.getText(id)
spannableStringToAnnotatedString(text, density)
}
}
private fun spannableStringToAnnotatedString(
text: CharSequence,
density: Density
): AnnotatedString {
return if (text is Spanned) {
with(density) {
buildAnnotatedString {
append((text.toString()))
text.getSpans(0, text.length, Any::class.java).forEach {
val start = text.getSpanStart(it)
val end = text.getSpanEnd(it)
when (it) {
is StyleSpan -> when (it.style) {
Typeface.NORMAL -> addStyle(
style = SpanStyle(
fontWeight = FontWeight.Normal,
fontStyle = FontStyle.Normal
),
start = start,
end = end
)
Typeface.BOLD -> addStyle(
style = SpanStyle(
fontWeight = FontWeight.Bold,
fontStyle = FontStyle.Normal
),
start = start,
end = end
)
Typeface.ITALIC -> addStyle(
style = SpanStyle(
fontWeight = FontWeight.Normal,
fontStyle = FontStyle.Italic
),
start = start,
end = end
)
Typeface.BOLD_ITALIC -> addStyle(
style = SpanStyle(
fontWeight = FontWeight.Bold,
fontStyle = FontStyle.Italic
),
start = start,
end = end
)
}
is TypefaceSpan -> addStyle(
style = SpanStyle(
fontFamily = when (it.family) {
FontFamily.SansSerif.name -> FontFamily.SansSerif
FontFamily.Serif.name -> FontFamily.Serif
FontFamily.Monospace.name -> FontFamily.Monospace
FontFamily.Cursive.name -> FontFamily.Cursive
else -> FontFamily.Default
}
),
start = start,
end = end
)
is BulletSpan -> {
Log.d("StringResources", "BulletSpan not supported yet")
addStyle(style = SpanStyle(), start = start, end = end)
}
is AbsoluteSizeSpan -> addStyle(
style = SpanStyle(fontSize = if (it.dip) it.size.dp.toSp() else it.size.toSp()),
start = start,
end = end
)
is RelativeSizeSpan -> addStyle(
style = SpanStyle(fontSize = it.sizeChange.em),
start = start,
end = end
)
is StrikethroughSpan -> addStyle(
style = SpanStyle(textDecoration = TextDecoration.LineThrough),
start = start,
end = end
)
is UnderlineSpan -> addStyle(
style = SpanStyle(textDecoration = TextDecoration.Underline),
start = start,
end = end
)
is SuperscriptSpan -> addStyle(
style = SpanStyle(baselineShift = BaselineShift.Superscript),
start = start,
end = end
)
is SubscriptSpan -> addStyle(
style = SpanStyle(baselineShift = BaselineShift.Subscript),
start = start,
end = end
)
is ForegroundColorSpan -> addStyle(
style = SpanStyle(color = Color(it.foregroundColor)),
start = start,
end = end
)
is Annotation -> {
if (it.key == URL_ANNOTATION_KEY) {
addStyle(
style = SpanStyle(color = Color.Blue),
start = start,
end = end
)
addStringAnnotation(
tag = "URL",
annotation = it.value,
start = start,
end = end
)
}
}
else -> addStyle(style = SpanStyle(), start = start, end = end)
}
}
}
}
} else {
AnnotatedString(text = text.toString())
}
}
@Composable
fun LinkableTextView(
@StringRes id: Int,
modifier: Modifier = Modifier,
style: TextStyle = MaterialTheme.typography.body1
) {
val uriHandler = LocalUriHandler.current
val annotatedString = annotatedStringResource(id)
ClickableText(
text = annotatedString,
style = style,
onClick = { offset ->
annotatedString.getStringAnnotations(
tag = "URL",
start = offset,
end = offset
).firstOrNull()?.let {
uriHandler.openUri(it.item)
}
},
modifier = modifier,
)
}
用法:
@Composable
fun MyComposableView {
LinkableTextView(
id = R.string.my_link_string
)
}
字符串资源:
<string name="my_link_string">Click this
<annotation url="https://www.whosebug.com">link</annotation>
to go to web site
</string>
还有一种“愚蠢”的方法,就是退回到使用 android.widget.TextView,它具有您正在寻找的行为:
@Composable
fun CompatHtmlTextView(@StringRes htmlStringResource: Int) {
val html = stringResource(htmlStringResource)
AndroidView(factory = { context ->
android.widget.TextView(context).apply {
text = fromHtml(html)
}
})
}