Jetpack Composeを使用して、Text関数にリンクを付加する方法について。
まず、表示するテキストをURLとURL以外に分割します。
/**
* @param type URLのときは"URL"、URL以外のときは"TEXT
* @param text 表示するテキスト
*/
data class TextOrUrlString(val type: String, val text: String)
/**
* textをURLとそれ以外に分割する
*/
private fun parseText(text: String): List<TextOrUrlString> {
val regex = Regex(
"""https?://[a-zA-Z\d!\?/+\-_~=;.,*&@#$%()'\[\]]+""",
setOf(RegexOption.IGNORE_CASE, RegexOption.MULTILINE)
)
val list: MutableList<TextOrUrlString> = mutableListOf()
var startIndex = 0
while (true) {
val matchResult = regex.find(text, startIndex = startIndex) ?: break
if (matchResult.range.first != 0) {
list.add(
TextOrUrlString(
"TEXT",
text.substring(startIndex, matchResult.range.first)
)
)
}
list.add(TextOrUrlString("URL", matchResult.value))
startIndex = matchResult.range.last + 1
}
if (startIndex < text.length) {
list.add(TextOrUrlString("TEXT", text.substring(startIndex)))
}
return list
}
URLとURL以外に分割したテキストから、AnnotatedStringを作成します。
/**
* @param parsedText URLとそれ以外に分割された文字列
* @param color リンク文字列の色
*/
private fun createAnnotatedString(
parsedText: List<TextOrUrlString>,
color: androidx.compose.ui.graphics.Color
): AnnotatedString {
return buildAnnotatedString {
parsedText.forEach { word ->
when (word.type) {
"TEXT" -> {
append(word.text)
}
"URL" -> {
pushStringAnnotation(
tag = "URL",
annotation = word.text
)
withStyle(
style = SpanStyle(
color = color,
fontWeight = FontWeight.Bold
)
) {
append(word.text)
}
pop()
}
}
}
}
}
タップされた位置を取得するためText関数の代わりに、ClickableText関数を使用します。
タップされた位置にあるURLを取得して、URLを開きます。
@Composable
fun AutoLinkText(text: String) {
val parsedText = parseText(text)
val annotatedString = createAnnotatedString(parsedText, MaterialTheme.colors.primary)
val uriHandler = AndroidUriHandler(LocalContext.current)
ClickableText(
text = annotatedString,
style = MaterialTheme.typography.body1,
onClick = { offset ->
annotatedString.getStringAnnotations(tag = "URL", start = offset, end = offset)
.firstOrNull()?.let { annotation ->
if (annotation.tag == "URL") {
uriHandler.openUri(annotation.item)
}
}
}
)
}
全体のソースコードは次のようになります。
import androidx.compose.foundation.text.ClickableText
import androidx.compose.material.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.AndroidUriHandler
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.withStyle
@Composable
fun AutoLinkText(text: String) {
val parsedText = parseText(text)
val annotatedString = createAnnotatedString(parsedText, MaterialTheme.colors.primary)
val uriHandler = AndroidUriHandler(LocalContext.current)
ClickableText(
text = annotatedString,
style = MaterialTheme.typography.body1,
onClick = { offset ->
annotatedString.getStringAnnotations(tag = "URL", start = offset, end = offset)
.firstOrNull()?.let { annotation ->
if (annotation.tag == "URL") {
uriHandler.openUri(annotation.item)
}
}
}
)
}
/**
* @param type URLのときは"URL"、URL以外のときは"TEXT
* @param text 表示するテキスト
*/
data class TextOrUrlString(val type: String, val text: String)
/**
* textをURLとそれ以外に分割する
*/
private fun parseText(text: String): List<TextOrUrlString> {
val regex = Regex(
"""https?://[a-zA-Z\d!\?/+\-_~=;.,*&@#$%()'\[\]]+""",
setOf(RegexOption.IGNORE_CASE, RegexOption.MULTILINE)
)
val list: MutableList<TextOrUrlString> = mutableListOf()
var startIndex = 0
while (true) {
val matchResult = regex.find(text, startIndex = startIndex) ?: break
if (matchResult.range.first != 0) {
list.add(
TextOrUrlString(
"TEXT",
text.substring(startIndex, matchResult.range.first)
)
)
}
list.add(TextOrUrlString("URL", matchResult.value))
startIndex = matchResult.range.last + 1
}
if (startIndex < text.length) {
list.add(TextOrUrlString("TEXT", text.substring(startIndex)))
}
return list
}
/**
* @param parsedText URLとそれ以外に分割された文字列
* @param color リンク文字列の色
*/
private fun createAnnotatedString(
parsedText: List<TextOrUrlString>,
color: androidx.compose.ui.graphics.Color
): AnnotatedString {
return buildAnnotatedString {
parsedText.forEach { word ->
when (word.type) {
"TEXT" -> {
append(word.text)
}
"URL" -> {
pushStringAnnotation(
tag = "URL",
annotation = word.text
)
withStyle(
style = SpanStyle(
color = color,
fontWeight = FontWeight.Bold
)
) {
append(word.text)
}
pop()
}
}
}
}
}
使用例
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
AutoLinkTextForJetpackComposeTheme {
// A surface container using the 'background' color from the theme
Surface(color = MaterialTheme.colors.background) {
MainContent()
}
}
}
}
}
@Composable
fun MainContent() {
val text = """
ブログ「山本隆の開発日誌」はhttp://www.gesource.jp/weblog/です。
""".trimIndent()
AutoLinkText(text = text)
}
@Preview(showBackground = true)
@Composable
fun DefaultPreview() {
AutoLinkTextForJetpackComposeTheme {
MainContent()
}
}