stringリソースの文字列にリンクを設定する - annotationを添えて

この記事の要約

  • AndroidのTextViewで文字列の一部にリンクを設定する方法について
  • annotationタグを使って、リンク設定をする文字列の範囲を指定する
  • 言語化対応やリンク変更の負担が少ない

やりたいこと

  • 図のようにTextViewの一部( こちら )にリンクを設定する
  • 文字列の範囲を指定する際にマジックナンバーを使用しない
  • タップ時はIntentを投げてブラウザなどに飛ばす

実装

stringリソースの定義

<string name="text_template">
  annotationタグのドキュメントは <annotation link="document_link">こちら</annotation> から飛べます
</string>
<string name="annotation_document">
  https://developer.android.com/guide/topics/resources/string-resource
</string>

リンクを設定したい文字列を <annotation xxx="yyy"> タグで囲みます
xxxyyy は任意の文字列を設定することが可能で、複数のannotationタグを設定した際にどの文字列かを判断するために使うことができます

リンクの設定

以下の手順でstringリソースからTextViewへセットするためのCharSequence を生成することができます

  1. 元となる文字列 R.string.text_template を取得
  2. text_template からannotationタグの情報を取り出す
  3. <annotation link="document_link"> を表すAnnotationインスタンスを取り出す
  4. 取り出したAnnotationから ClickableSpan を作成する
  5. Annotationに含まれている文字列のポジションを使って ClickableSpan を適用する範囲を指定する

コード例

private fun getText(): CharSequence {
  val template = getText(R.string.text_template)
  if (template !is SpannedString) return template

  // Annotationの使い方 https://developer.android.com/guide/topics/resources/string-resource#StylingWithAnnotations
  val annotations = template.getSpans(0, template.length, Annotation::class.java)
  val spannableString = SpannableString(template)

  annotations
    .filter { annotation -> annotation.key == "link" }
    .filter { linkAnnotation -> linkAnnotation.value == "document_link" }
    .forEach { annotation ->
      val linkSpan = object : ClickableSpan() {
        override fun onClick(widget: View) {
          val url = getString(R.string.annotation_document)
          intent = viewIntent(url)
          startActivity(intent)
        }
      }
      spannableString.setSpan(
        linkSpan,
        template.getSpanStart(annotation),
        template.getSpanEnd(annotation),
        Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
      )
    }
  return spannableString
}

fun viewIntent(url: String): Intent {
  return Intent(Intent.ACTION_VIEW).apply {
    data = Uri.parse(url)
  }
}

メリット

annotationタグを使用すると文字列の範囲を Annotation インスタンスから取得できるため、文字列の変更を行ってもstringリソースのみの変更で済みます
また、多言語対応する場合にもannotationタグの属性と値を揃えておくことで、コードの変更が不要になるケースが増えます

参考

String resources  |  Android Developers