有什么方法可以使用 JetPack Compose Text 上的 android:autoLink 功能吗?
我知道,在一个简单的标签/修饰符中使用此功能可能不是“声明性方式”,但也许有一些简单的方法?
对于文本样式,我可以使用这种方式
val apiString = AnnotatedString.Builder("API provided by")
apiString.pushStyle(
style = SpanStyle(
color = Color.Companion.Blue,
textDecoration = TextDecoration.Underline
)
)
apiString.append("https://example.com")
Text(text = apiString.toAnnotatedString())
但是,我如何管理这里的点击?如果我以编程方式说出我期望系统(电子邮件、电话、网络等)的行为,那就太好了。喜欢它。与 TextView 一起使用。 谢谢你
我们可以在 Android Compose 中实现 Linkify 之类的
TextView
,如下例所示,
@Composable
fun LinkifySample() {
val uriHandler = UriHandlerAmbient.current
val layoutResult = remember {
mutableStateOf<TextLayoutResult?>(null)
}
val text = "API provided by"
val annotatedString = annotatedString {
pushStyle(
style = SpanStyle(
color = Color.Companion.Blue,
textDecoration = TextDecoration.Underline
)
)
append(text)
addStringAnnotation(
tag = "URL",
annotation = "https://example.com",
start = 0,
end = text.length
)
}
Text(
fontSize = 16.sp,
text = annotatedString, modifier = Modifier.tapGestureFilter { offsetPosition ->
layoutResult.value?.let {
val position = it.getOffsetForPosition(offsetPosition)
annotatedString.getStringAnnotations(position, position).firstOrNull()
?.let { result ->
if (result.tag == "URL") {
uriHandler.openUri(result.item)
}
}
}
},
onTextLayout = { layoutResult.value = it }
)
}
在上面的示例中,我们可以看到我们给出了文本,并且我们使用
addStringAnnotation
来设置标签。然后使用tapGestureFilter
,我们可以获得点击的注释。
最后使用
UriHandlerAmbient.current
我们可以打开电子邮件、电话或网络等链接。
参考:https://www.hellsoft.se/rendering-markdown-with-jetpack-compose/
jetpack compose 最重要的部分是与原生 android 组件的兼容性。
创建一个使用 TextView 的组件并使用它:
@Composable
fun DefaultLinkifyText(modifier: Modifier = Modifier, text: String?) {
val context = LocalContext.current
val customLinkifyTextView = remember {
TextView(context)
}
AndroidView(modifier = modifier, factory = { customLinkifyTextView }) { textView ->
textView.text = text ?: ""
LinkifyCompat.addLinks(textView, Linkify.ALL)
Linkify.addLinks(textView, Patterns.PHONE,"tel:",
Linkify.sPhoneNumberMatchFilter, Linkify.sPhoneNumberTransformFilter)
textView.movementMethod = LinkMovementMethod.getInstance()
}
}
使用方法:
DefaultLinkifyText(
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight(),
text = "6999999 and https://stackoverflow.com/ works fine"
)
对于任何有兴趣使用纯 Kotlin Compose 代码和最新的
withLink
的人,我制作了一个 utils 函数,可以帮助您构建一个带注释的字符串,其中会自动检测到链接、电子邮件和电话。
/** Email address pattern, same as [android.util.Patterns.EMAIL_ADDRESS] */
val emailRegex = """[a-zA-Z0-9+._%-+]{1,256}@[a-zA-Z0-9][a-zA-Z0-9-]{0,64}(\.[a-zA-Z0-9][a-zA-Z0-9-]{0,25})+""".toRegex()
/** URL pattern, no HTTP or HTTPS needed */
val urlRegex = """(?<=^|\s)[^\s@]+\.\S+(?=${'$'}|\s)""".toRegex()
/** URL pattern, no HTTP or HTTPS needed */
val phoneNumberRegex = """\+?\d{1,4}?[\s-]?\(?(\d{1,4})\)?[\s-]?\d{1,4}[\s-]?\d{1,4}[\s-]?\d{1,9}""".toRegex()
/**
* Clickable text supporting <a href> HTML tags and can also match email, URL addresses, and phone numbers if needed
* @param linkStyles styles applied to the found link
* @param matchEmail whether an email should be considered a link
* @param matchPhone whether a phone number should be considered a link
* @param matchUrl whether an url should be considered a link
* @param onLinkClicked called whenever a link was clicked, propagating the link including prefix for phone number and email
*/
@Composable
fun buildAnnotatedLinkString(
text: String,
linkStyles: TextLinkStyles = LocalTheme.current.styles.link,
matchEmail: Boolean = true,
matchPhone: Boolean = true,
matchUrl: Boolean = true,
onLinkClicked: (link: String) -> Unit
)= buildAnnotatedString {
// first, we replace all matches with according href link
val annotatedText = text.run {
var replaced = this
if(matchEmail) {
replaced = replaced.replace(emailRegex, transform = { res ->
"<a href=\"mailto:${res.value}\">${res.value}<a/>"
})
}
if(matchUrl) {
replaced = replaced.replace(urlRegex, transform = { res ->
"<a href=\"${res.value}\">${res.value}<a/>"
})
}
if(matchPhone) {
replaced = replaced.replace(phoneNumberRegex, transform = { res ->
"<a href=\"tel:${res.value}\">${res.value}<a/>"
})
}
replaced
}
var appendableText = ""
var linkIteration = false
annotatedText.forEach { c ->
// may be a beginning of a tag, let's clear backstack to simply conditions
if(!linkIteration && c == '<') {
append(appendableText)
appendableText = ""
}
// beginning of link tag
if(appendableText == "<a href=\"") {
// append text before the link
append(appendableText.removeSuffix("<a href=\""))
appendableText = ""
linkIteration = true
}
// end of link tag, all we have at this point is very likely LINK">CONTENT<a/
if(linkIteration && c == '>' && appendableText.last() == '/') {
appendableText.let { localAppendedText ->
withLink(
link = LinkAnnotation.Clickable(
tag = "ACTION",
styles = linkStyles,
linkInteractionListener = {
onLinkClicked(
localAppendedText.substring(
startIndex = 0,
endIndex = localAppendedText.indexOfLast { it == '"' }
)
)
},
),
) {
append(
localAppendedText.substring(
startIndex = localAppendedText.indexOf('>') + 1,
endIndex = localAppendedText.indexOf("<")
)
)
}
}
appendableText = ""
linkIteration = false
}else {
appendableText += c
}
}
append(appendableText)
}
这对于使用 Kotlin Multiplatform 的人员特别有用,因为不依赖于 Android 或 Java 库。