与撰写文本链接

Linkify with Compose Text

我找不到如何使用 Jetpack Compose 链接我的 Text()

在撰写之前我所要做的就是:

Linkify.addLinks(myTextView, Linkify.EMAIL_ADDRESSES or Linkify.WEB_URLS)

显然,我的 TextView 中包含的所有链接都变成了可点击的链接。

重要提示:文本内容来自API,链接没有固定位置,内容可能包含多个链接。

我想通过使用 Jetpack Compose 来保持这种行为,但我找不到任何关于这样做的信息。

有人知道吗?

您可以使用 AnnotatedString 来实现此行为。

文档:https://developer.android.com/reference/kotlin/androidx/compose/ui/text/AnnotatedString

另外,这个可能对你有帮助:

如果有人正在寻找解决方案,以下内容将使您的文本中的任何链接都可点击并设置样式:

@Composable
fun LinkifyText(text: String, modifier: Modifier = Modifier) {
    val uriHandler = LocalUriHandler.current
    val layoutResult = remember {
        mutableStateOf<TextLayoutResult?>(null)
    }
    val linksList = extractUrls(text)
    val annotatedString = buildAnnotatedString {
        append(text)
        linksList.forEach {
            addStyle(
                    style = SpanStyle(
                            color = Color.Companion.Blue,
                            textDecoration = TextDecoration.Underline
                    ),
                    start = it.start,
                    end = it.end
            )
            addStringAnnotation(
                    tag = "URL",
                    annotation = it.url,
                    start = it.start,
                    end = it.end
            )
        }
    }
    Text(text = annotatedString, style = MaterialTheme.typography.body1, modifier = modifier.pointerInput(Unit) {
    detectTapGestures { 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 }
    )
}

private val urlPattern: Pattern = Pattern.compile(
        "(?:^|[\W])((ht|f)tp(s?):\/\/|www\.)"
                + "(([\w\-]+\.){1,}?([\w\-.~]+\/?)*"
                + "[\p{Alnum}.,%_=?&#\-+()\[\]\*$~@!:/{};']*)",
        Pattern.CASE_INSENSITIVE or Pattern.MULTILINE or Pattern.DOTALL
)

fun extractUrls(text: String): List<LinkInfos> {
    val matcher = urlPattern.matcher(text)
    var matchStart: Int
    var matchEnd: Int
    val links = arrayListOf<LinkInfos>()

    while (matcher.find()) {
        matchStart = matcher.start(1)
        matchEnd = matcher.end()

        var url = text.substring(matchStart, matchEnd)
        if (!url.startsWith("http://") && !url.startsWith("https://"))
            url = "https://$url"

        links.add(LinkInfos(url, matchStart, matchEnd))
    }
    return links
}

data class LinkInfos(
        val url: String,
        val start: Int,
        val end: Int
)

我认为现在更好的解决方案是使用这样的 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()
    }
}

根据以上回答, 您可以使用 https://github.com/firefinchdev/linkify-text

它是一个文件,您可以直接将其复制到您的项目中。

另外,它使用Android的Linkify进行link检测,与TextViewautoLink相同。

我使用了一个类似但更简单的解决方案来获得正确的Material 设计外观:

@Composable
fun BodyWithLinks(body: String, modifier: Modifier = Modifier) {
  AndroidView(
    modifier = modifier,
    factory = { context ->
      (MaterialTextView(context) as AppCompatTextView).apply {
        val spannableString = SpannableString(body)
        Linkify.addLinks(spannableString, Linkify.ALL)
        text = spannableString
        setTextAppearance(R.style.Theme_Body_1)
      }
    },
  )
}

这是一个示例,如果您在一个句子中有多个可点击的词并且您想在应用程序内导航:

    @Composable
    fun InformativeSignUpText() {
        val informativeText = stringResource(R.string.sign_up_already_have_an_account)
        val logInSubstring = stringResource(R.string.general_log_in)
        val supportSubstring = stringResource(R.string.general_support)
        val logInIndex = informativeText.indexOf(logInSubstring)
        val supportIndex = informativeText.indexOf(supportSubstring)
    
        val informativeAnnotatedText = buildAnnotatedString {
            append(informativeText)
            addStyle(
                style = SpanStyle(
                    color = MaterialTheme.colors.primary
                ),
                start = logInIndex,
                end = logInIndex + logInSubstring.length
            )
            addStringAnnotation(
                tag = logInSubstring,
                annotation = logInSubstring,
                start = logInIndex,
                end = logInIndex + logInSubstring.length
            )
            addStyle(
                style = SpanStyle(
                    color = MaterialTheme.colors.primary
                ),
                start = supportIndex,
                end = supportIndex + supportSubstring.length
            )
            addStringAnnotation(
                tag = supportSubstring,
                annotation = supportSubstring,
                start = supportIndex,
                end = supportIndex + supportSubstring.length
            )
        }
        ClickableText(
            modifier = Modifier.padding(
                top = 16.dp
            ),
            style = MaterialTheme.typography.subtitle1.copy(
                color = Nevada
            ),
            text = informativeAnnotatedText,
            onClick = { offset ->
                informativeAnnotatedText.getStringAnnotations(
                    tag = logInSubstring,
                    start = offset,
                    end = offset
                ).firstOrNull()?.let {
                    Log.d("mlogs", it.item)
                }
    
                informativeAnnotatedText.getStringAnnotations(
                    tag = supportSubstring,
                    start = offset,
                    end = offset
                ).firstOrNull()?.let {
                    Log.d("mlogs", it.item)
                }
            }
        )
   }