我有随机纯文本,如下例所示。
Android is a mobile operating system https://www.android.com/ based on a modified version of the Linux kernel http://hellosite.com/4dnfsddp and other open-source software.
我需要将其添加到可组合文本(或 ClickableText)并检测那里的 URL 并使 URL 可单击并突出显示,如下图所示。
我已经制作了一个可组合项,您可以像
Text
可组合项一样轻松使用它。
用法示例
@Composable
fun Stack024() {
LinkifyText(
modifier = Modifier.padding(10.dp),
text = "Android is a mobile operating system https://www.android.com/ based on a modified version of the Linux kernel http://hellosite.com/4dnfsddp and other open-source software.\n",
fontSize = 14.sp,
onClickLink = {
if (it == "") {
//Item Click
}
}
)
}
Linkify 可组合
import android.os.Build
import android.text.SpannableString
import android.text.style.URLSpan
import android.text.util.Linkify
import android.util.Log
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.material3.LocalTextStyle
import androidx.compose.material3.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 ->
val item = annotatedString.getStringAnnotations(
start = offset,
end = offset,
).firstOrNull()
if(linkEntire){
Log.e("TAG", "LinkifyText: Entire Link" )
onClickLink?.invoke("")
}else{
if(item != null) {
uriHandler.openUri(item.item)
onClickLink?.invoke(annotatedString.substring(item.start, item.end))
}else{
Log.e("TAG", "LinkifyText: Entire Link" )
onClickLink?.invoke("")
}
}
}
)
} 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))
}
}
}
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))
}
}