AndroidのTextViewの高さを考慮してフォントサイズを自動調整する

今回はAndroidのTextViewのフォントサイズの自動調整に関するお話です。

<img width="606" alt="スクリーンショット 2019-05-19 22.57.38.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/362719/3bee7e68-7c17-d66c-8d48-20eabb952bdf.png">

環境：Android Studio 3.3.1、Kotlin 1.3

AndroidでもAndroid 8.0(API Level 26)から[AutoSizing TextViews](https://developer.android.com/guide/topics/ui/look-and-feel/autosizing-textview)が追加されました。
しかしレイアウトの都合でTextViewのwidthを"wrap_content"にしなければならない場合はこのAutoSizing TextViewsでもフォントが自動調整されません。
そこで以下のサイトなどを参考に自分でフォントサイズを調整することが多いかと思うのですが、

[【Android】横幅に合わせてテキストサイズを調整する](https://gist.github.com/STAR-ZERO/2934490)

この方法の場合、TextViewが複数行表示できる高さを持っている時にも、テキストを1行で表示する大きさまでフォントサイズを小さくしてしまいます。
TextViewがテキストを複数行表示できる高さを持っている場合は、テキストを複数行にしてぎりぎり表示できるフォントサイズに留めておきたいですよね。
そこで上記のサイトのコードに少し手を加えて以下の様にしてみました。

#コード
```Kotlin:AutoFontTextView.kt
import android.content.Context
import android.graphics.Paint
import android.util.AttributeSet
import android.util.TypedValue
import android.widget.TextView

class AutoFontFitTextView @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : TextView(context, attrs, defStyleAttr) {

    override fun onLayout(changed: Boolean, left:Int, top: Int, right: Int, bottom: Int) {
        super.onLayout(changed, left, top, right, bottom)
        resize()
    }

    private fun resize() {

        val viewHeight = this.height
        val viewWidth = this.width
        var textSize = this.textSize

        var textHeight = calcTextHeight(textSize,viewWidth)

        while (viewHeight <= textHeight) {
            if (1f >= textSize) {
                textSize = 1f
                break
            }

            textSize -= 1f
            textHeight = calcTextHeight(textSize,viewWidth)
        }

        setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize)
    }

    private fun calcTextHeight(textSize:Float,viewWidth:Int): Float {
        val paint = Paint()
        paint.textSize = textSize
        val textHeightPerRow = paint.getFontMetrics(null)
        val textPerLines = this.text.toString().split("\n")

        var rowCount = 0

        for (i in 0..textPerLines.size - 1) {
            val textWidth = paint.measureText(textPerLines[i])
            rowCount += Math.ceil(textWidth.toDouble() / viewWidth.toDouble()).toInt()
            if (i != textPerLines.size - 1) {
                rowCount += 1
            }
        }
        return textHeightPerRow * rowCount
    }
}
```

#解説
Paintを利用してテキストのサイズを測るところは同じです。
テキストサイズをPaintに設定した後、以下のコードでテキストの1行あたりの高さが取れます。

```Kotlin
val textHeightPerRow = paint.getFontMetrics(null)
```

次にテキストを表示するために必要な行数を割り出します。
今回はテキストに改行文字が入っているケースも想定しています。

```Kotlin
val textPerLines = this.text.toString().split("\n")
var rowCount = 0

for (i in 0..textPerLines.size - 1) {
    val textWidth = paint.measureText(textPerLines[i])
    rowCount += Math.ceil(textWidth.toDouble() / viewWidth.toDouble()).toInt()
    if (i != textPerLines.size - 1) {
         rowCount += 1
    }
}
```

「1行あたりの高さ * 必要な行数」でテキストの高さが得られますので、それがTextViewの高さ以内に収まるまでフォントサイズを下げます。

#GitHub
今回のサンプルコードは以下のGitHubで公開しています。

https://github.com/naosekig/AutoFontFitTextView