【Android】SpannableStringを使って文字列に画像を挿入する

##はじめに
この記事はand factory Advent Calendar 2020 の12日目の記事です。
昨日は@k_shinnさんの [【Android】ちょっと便利なDrawableを書く](【Android】ちょっと便利なDrawableを書く) でした。
##やりたいこと
- 画像を文字列として扱いたい

##背景
Androidアプリを作っていく中で「テキストの横に画像を表示したいんだけど〜」
という旨のオーダーをもらうことがありまして
「そんなの簡単です！」と素直〜にImageViewの横にTextViewを置いて…と下記xmlで実装しました。

```hoge.xml
    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginStart="20dp"
        android:layout_marginEnd="20dp"
        android:layout_marginBottom="20dp"
        android:background="@color/background">

        <ImageView
            android:id="@+id/icon"
            android:layout_width="20dp"
            android:layout_height="20dp"
            android:src="@mipmap/ic_launcher"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

        <TextView
            android:id="@+id/repository_name"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:ellipsize="end"
            android:textSize="20sp"
            android:textStyle="bold"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toEndOf="@+id/icon"
            app:layout_constraintTop_toTopOf="parent"
            tools:text="テキストテキストテキスト" />
    </androidx.constraintlayout.widget.ConstraintLayout>
```
できた！
![スクリーンショット 2020-12-11 23.58.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/491667/749f7cc3-4a6b-e9e8-5daf-fdcfd0cfbaad.png)


ですが長い文字列が来た際に困ったことが起きました。
そうです、文字の折り返し地点が不自然なものとなってしまうのです…！

![スクリーンショット 2020-12-12 0.02.34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/491667/641e8b3f-f49b-2d36-27c1-2611ea53f462.png)

今回求められていた要件としては
文字列の２行目の位置がアイコンの真下に来るイメージのものが求められていました。

★hogehoge
hogehoge

そのため、SpannableStringでちょっとゴリった実装をしていくことにしました。

ザックリ言ってしまうと画像を挿入する分の文字スペースを作って
そのスペースにSpannableStringで画像をねじ込んでいくスタイルです。

##コード

```IconSpannable.kt
class IconSpannable(private val targetTextView: TextView) {

    fun insertIconPrefix(@DrawableRes prefixDrawableRes: Int, sourceString: String): SpannableString {
        // 画像を挿入するため、一文字分の空白を作る
        val resultString = SpannableString("  $sourceString")
        return insertImage(prefixDrawableRes, resultString, 0, 1)
    }

    private fun insertImage(
        @DrawableRes drawableRes: Int,
        resultString: SpannableString,
        startSpan: Int,
        endSpan: Int
    ): SpannableString {
        val context = targetTextView.context
        // 対象のTextViewの高さを計測
        val size = (targetTextView.paint.descent() - targetTextView.paint.ascent()).toInt()
        val drawable = context.resources.getDrawable(drawableRes, null)
        drawable?.let {
            it.setBounds(0, 0, size, size)
            val span = ImageSpan(it)
            resultString.setSpan(span, startSpan, endSpan, Spanned.SPAN_EXCLUSIVE_INCLUSIVE)
        }
        return resultString
    }
}

```

###使い方

```Kotlin
    binding.repositoryName.text =
        IconSpannable(binding.repositoryName).insertIconPrefix(
            R.mipmap.ic_launcher,
            "テキストテキストテキストテキストテキストテキスト"
        )
```


###結果
無事にTextViewの２行目が画像の下にでてきてくれました:v:
![Screenshot_20201212-012349.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/491667/9eeed907-e827-4616-e2ee-4ca9bab7dcae.png)


## 終わりに
以上、SpannableStringを使って文字列に画像を挿入する方法でした！
単純そうな要件であっても意外とめんどくさいのがAndroid、というよりエンジニアの常ですよね。
どなたかの助けになれば幸いです。