How can I get the number of lines a string will take up in a TextView before it is rendered.
A ViewTreeObserver will not work because thos
Thanks to Eugene Popovich I got:
import android.os.Build
import android.text.Layout
import android.text.StaticLayout
import android.text.TextDirectionHeuristic
import android.text.TextPaint
import android.widget.TextView
object TextMeasurementUtil {
/**
* Split text into lines using specified parameters and the same algorithm
* as used by the [TextView] component
*
* @param text the text to split
* @param params the measurement parameters
* @return
*/
fun getTextLines(text: CharSequence, params: TextViewParams): List {
val layout = getStaticLayout(text, params)
return (0 until layout.lineCount).map {
layout.text.subSequence(layout.getLineStart(it), layout.getLineEnd(it))
}
}
fun getTextLineCount(text: CharSequence, params: TextViewParams): Int {
val layout = getStaticLayout(text, params)
return layout.lineCount
}
fun getTextLines(textView: TextView): List {
val layout = getStaticLayout(textView)
return (0 until layout.lineCount).map {
layout.text.subSequence(layout.getLineStart(it), layout.getLineEnd(it))
}
}
fun getTextLineCount(textView: TextView): Int {
val layout = getStaticLayout(textView)
return layout.lineCount
}
/**
* The text measurement parameters
*/
fun getTextViewParams(textView: TextView): TextViewParams {
val layout = textView.layout
val width = textView.width - textView.compoundPaddingLeft - textView.compoundPaddingRight
var lineSpacingExtra = 0f
var lineSpacingMultiplier = 1.0f
var includeFontPadding = true
var breakStrategy = 0
var hyphenationFrequency = 0
var justificationMode = 0
var useFallbackLineSpacing = false
var textDirectionHeuristic: TextDirectionHeuristic? = null
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
lineSpacingExtra = textView.lineSpacingExtra
lineSpacingMultiplier = textView.lineSpacingMultiplier
includeFontPadding = textView.includeFontPadding
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
breakStrategy = textView.breakStrategy
hyphenationFrequency = textView.hyphenationFrequency
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
justificationMode = textView.justificationMode
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
useFallbackLineSpacing = textView.isFallbackLineSpacing
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
textDirectionHeuristic = textView.textDirectionHeuristic
}
}
return TextViewParams(
textPaint = layout.paint,
alignment = layout.alignment,
lineSpacingExtra = lineSpacingExtra,
lineSpacingMultiplier = lineSpacingMultiplier,
includeFontPadding = includeFontPadding,
breakStrategy = breakStrategy,
hyphenationFrequency = hyphenationFrequency,
justificationMode = justificationMode,
useFallbackLineSpacing = useFallbackLineSpacing,
textDirectionHeuristic = textDirectionHeuristic,
width = width
)
}
private fun getStaticLayout(text: CharSequence,
params: TextViewParams): StaticLayout =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
val builder = StaticLayout.Builder
.obtain(text, 0, text.length, params.textPaint, params.width)
.setAlignment(params.alignment)
.setLineSpacing(params.lineSpacingExtra, params.lineSpacingMultiplier)
.setIncludePad(params.includeFontPadding)
.setBreakStrategy(params.breakStrategy)
.setHyphenationFrequency(params.hyphenationFrequency)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
builder.setJustificationMode(params.justificationMode)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
builder.setUseLineSpacingFromFallbacks(params.useFallbackLineSpacing)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
builder.setTextDirection(params.textDirectionHeuristic!!)
}
builder.build()
} else {
@Suppress("DEPRECATION")
StaticLayout(
text,
params.textPaint,
params.width,
params.alignment,
params.lineSpacingMultiplier,
params.lineSpacingExtra,
params.includeFontPadding)
}
private fun getStaticLayout(textView: TextView): StaticLayout =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
val builder = StaticLayout.Builder
.obtain(textView.text, 0, textView.text.length, textView.layout.paint,
textView.width)
.setAlignment(textView.layout.alignment)
.setLineSpacing(textView.lineSpacingExtra, textView.lineSpacingMultiplier)
.setIncludePad(textView.includeFontPadding)
.setBreakStrategy(textView.breakStrategy)
.setHyphenationFrequency(textView.hyphenationFrequency)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
builder.setJustificationMode(textView.justificationMode)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
builder.setUseLineSpacingFromFallbacks(textView.isFallbackLineSpacing)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
builder.setTextDirection(textView.textDirectionHeuristic)
}
builder.build()
} else {
@Suppress("DEPRECATION")
StaticLayout(
textView.text,
textView.layout.paint,
textView.width,
textView.layout.alignment,
textView.lineSpacingMultiplier,
textView.lineSpacingExtra,
textView.includeFontPadding)
}
data class TextViewParams(
val textPaint: TextPaint,
val alignment: Layout.Alignment,
val lineSpacingExtra: Float,
val lineSpacingMultiplier: Float,
val includeFontPadding: Boolean,
val breakStrategy: Int,
val hyphenationFrequency: Int,
val justificationMode: Int,
val useFallbackLineSpacing: Boolean,
val textDirectionHeuristic: TextDirectionHeuristic?,
val width: Int
)
}
Usage:
If you want to print different texts in equal TextViews (for instance, in RecyclerView with one or similar ViewHolders):
val params = TextMeasurementUtil.getTextViewParams(textView)
val lines = TextMeasurementUtil.getTextLines(textView.text, params)
val count = TextMeasurementUtil.getTextLineCount(textView.text, params)
In any other case:
val lines = TextMeasurementUtil.getTextLines(textView)
val count = TextMeasurementUtil.getTextLineCount(textView)
In RecyclerView you won't know parameters of TextView until you call post or doOnPreDraw method, so use:
textView.doOnPreDraw {
val lines = TextMeasurementUtil.getTextLines(textView)
val count = TextMeasurementUtil.getTextLineCount(textView)
}