Android 撰写文本的自动链接

问题描述 投票:0回答:3

有什么方法可以使用 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 textview android-jetpack-compose
3个回答
5
投票

我们可以在 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/


3
投票

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"
 )

0
投票

对于任何有兴趣使用纯 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 库。

© www.soinside.com 2019 - 2024. All rights reserved.