TextViewに設定したテキスト内のURLに遷移する
TextViewに設定したテキスト内にURLがあった場合に、そのリンクをクリックできるようにしたい。 クリックしたらブラウザが開いて該当ページに移動できるようにしたい。
手っ取り早くこの要望を満たそうと思ったら、TextViewには便利な機能が用意されている。
TextViewにandroid:autoLink="web"を追加すればよいだけである。
これでテキスト内のURLをクリックしたらブラウザが開いてくれる。
めでたしめでたし。
といくなら楽でよかったのだが、この機能によるURLの処理はあまり正確ではない。

URLが半角スペースで区切られていたり、2バイト文字以外で区切られていたりしたら正しくリンクとして拾ってもらえる。
例えばあいうえお https://android.gcreate.jp/ かきくけこというテキストであればURLの部分のみがURLとして識別される。
しかしあいうえおhttps://android.gcreate.jp/だとリンクを拾ってくれない。
またあいうえお https://android.gcreate.jp/がリンクになってほしいとした場合、「がリンクになってほしい」という部分までURLとして拾われてしまう。
2バイト文字でない半角カッコで囲ってかっこで(https://android.gcreate.jp/)とした場合、閉じカッコもURLに含まれてしまう。
URLの抽出がうまくいかない場合があるのが最大の問題であるが、URLのハンドリングをカスタマイズできないのもちょっと不便である。 例えばChromeカスタムタブでリンクを開きたい場合に、autoLinkでは対応できない。 autoLinkの場合、リンクをクリックするとACTION_VIEWの暗黙的インテントが発行される。
以上の問題点を回避するには、自分でテキストにClickableSpanを設定してやると良い。
- テキストをSpannableStringに変換する
- テキストから正規表現を利用してURLを抽出する
- 抽出したURLを用いてSpannableStringにClickableSpanを設定する
- TextViewにSpannableStringをsetTextで設定する
- TextViewにsetMovementMethodを設定する
コード的にはこんな感じ(Kotlinとandroid-ktxを利用している)。
val text = "あいうえおhttps://android.gcreate.jp/かきくけこ"
val spannable = text.toSpannable()
val matcher = Pattern.compile("(https?|ftp|file)://[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|]").matcher(text)
while (matcher.find()) {
val url = matcher.group()
val start = matcher.start()
val end = matcher.end()
spannable.setSpan(MyUrlSpan(url), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
}
textView.text = spannable
textView.movementMethod = LinkMovementMethod.getInstance()
MyUrlSpanは自分で定義する。 といっても大したことはやっていない。
class MyUrlSpan(val url: String) : ClickableSpan() {
override fun onClick(view: View) {
Snackbar.make(view, "$url clicked", Snackbar.LENGTH_SHORT).show()
}
}
SnackbarでURLを表示しているだけ。 リンクをクリックした際の挙動はこのonClickでカスタマイズできる。
