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 those are only fired after it is rendered.
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 those are only fired after it is rendered.
Rect bounds = new Rect();
Paint paint = new Paint();
paint.setTextSize(currentTextSize);
paint.getTextBounds(testString, 0, testString.length(), bounds);
Now divide the width of text with the width of your TextView to get the total number of lines.
int numLines = (int) Math.ceil((float) bounds.width() / currentSize);
The accepted answer doesn't work when a whole word is placed on the next line in order to avoid breaking the word:
|hello |
|world! |
The only way to be 100% sure about the number of lines is to use the same text flow engine that TextView uses. Since TextView doesn't share its re-flow logic here's a custom string processor which splits text into multiple lines each of which fits the given width. It also does its best to not break the words unless the whole word does not fit:
public List<String> splitWordsIntoStringsThatFit(String source, float maxWidthPx, Paint paint) {
ArrayList<String> result = new ArrayList<>();
ArrayList<String> currentLine = new ArrayList<>();
String[] sources = source.split("\\s");
for(String chunk : sources) {
if(paint.measureText(chunk) < maxWidthPx) {
processFitChunk(maxWidthPx, paint, result, currentLine, chunk);
} else {
//the chunk is too big, split it.
List<String> splitChunk = splitIntoStringsThatFit(chunk, maxWidthPx, paint);
for(String chunkChunk : splitChunk) {
processFitChunk(maxWidthPx, paint, result, currentLine, chunkChunk);
}
}
}
if(! currentLine.isEmpty()) {
result.add(TextUtils.join(" ", currentLine));
}
return result;
}
/**
* Splits a string to multiple strings each of which does not exceed the width
* of maxWidthPx.
*/
private List<String> splitIntoStringsThatFit(String source, float maxWidthPx, Paint paint) {
if(TextUtils.isEmpty(source) || paint.measureText(source) <= maxWidthPx) {
return Arrays.asList(source);
}
ArrayList<String> result = new ArrayList<>();
int start = 0;
for(int i = 1; i <= source.length(); i++) {
String substr = source.substring(start, i);
if(paint.measureText(substr) >= maxWidthPx) {
//this one doesn't fit, take the previous one which fits
String fits = source.substring(start, i - 1);
result.add(fits);
start = i - 1;
}
if (i == source.length()) {
String fits = source.substring(start, i);
result.add(fits);
}
}
return result;
}
/**
* Processes the chunk which does not exceed maxWidth.
*/
private void processFitChunk(float maxWidth, Paint paint, ArrayList<String> result, ArrayList<String> currentLine, String chunk) {
currentLine.add(chunk);
String currentLineStr = TextUtils.join(" ", currentLine);
if (paint.measureText(currentLineStr) >= maxWidth) {
//remove chunk
currentLine.remove(currentLine.size() - 1);
result.add(TextUtils.join(" ", currentLine));
currentLine.clear();
//ok because chunk fits
currentLine.add(chunk);
}
}
Here's a part of a unit test:
String text = "Hello this is a very long and meanless chunk: abcdefghijkonetuhosnahrc.pgraoneuhnotehurc.pgansohtunsaohtu. Hope you like it!";
Paint paint = new Paint();
paint.setTextSize(30);
paint.setTypeface(Typeface.DEFAULT_BOLD);
List<String> strings = splitWordsIntoStringsThatFit(text, 50, paint);
assertEquals(3, strings.size());
assertEquals("Hello this is a very long and meanless chunk:", strings.get(0));
assertEquals("abcdefghijkonetuhosnahrc.pgraoneuhnotehurc.pganso", strings.get(1));
assertEquals("htunsaohtu. Hope you like it!", strings.get(2));
Now one can be 100% sure about the line count in TextView without a need to render it:
TextView textView = ... //text view must be of fixed width
Paint paint = new Paint();
paint.setTextSize(yourTextViewTextSizePx);
paint.setTypeface(yourTextViewTypeface);
float textViewWidthPx = ...;
List<String> strings = splitWordsIntoStringsThatFit(yourText, textViewWidthPx, paint);
textView.setText(TextUtils.join("\n", strings);
int lineCount = strings.size(); //will be the same as textView.getLineCount()
If you know or can determine the width of the TextView's parent, you are able to invoke a view measurement which results in line count being calculated.
val parentWidth = PARENT_WIDTH // assumes this is known/can be found
myTextView.measure(
MeasureSpec.makeMeasureSpec(parentWidth, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED))
The TextView's layout
is no longer null and you can check the calculated line count with myTextView.lineCount
.
Reference: Getting height of text view before rendering to layout
Get line of TextView before rendering.
This is my code base the link above. It's working for me.
private int widthMeasureSpec;
private int heightMeasureSpec;
private int heightOfEachLine;
private int paddingFirstLine;
private void calculateHeightOfEachLine() {
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
Display display = wm.getDefaultDisplay();
Point size = new Point();
display.getSize(size);
int deviceWidth = size.x;
widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(deviceWidth, View.MeasureSpec.AT_MOST);
heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
//1 line = 76; 2 lines = 76 + 66; 3 lines = 76 + 66 + 66
//=> height of first line = 76 pixel; height of second line = third line =... n line = 66 pixel
int heightOfFirstLine = getHeightOfTextView("A");
int heightOfSecondLine = getHeightOfTextView("A\nA") - heightOfFirstLine;
paddingFirstLine = heightOfFirstLine - heightOfSecondLine;
heightOfEachLine = heightOfSecondLine;
}
private int getHeightOfTextView(String text) {
// Getting height of text view before rendering to layout
TextView textView = new TextView(context);
textView.setPadding(10, 0, 10, 0);
//textView.setTypeface(typeface);
textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, context.getResources().getDimension(R.dimen.tv_size_14sp));
textView.setText(text, TextView.BufferType.SPANNABLE);
textView.measure(widthMeasureSpec, heightMeasureSpec);
return textView.getMeasuredHeight();
}
private int getLineCountOfTextViewBeforeRendering(String text) {
return (getHeightOfTextView(text) - paddingFirstLine) / heightOfEachLine;
}
Note: This code also must be set for real textview on screen
textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, context.getResources().getDimension(R.dimen.tv_size_14sp));
You should use the new TextViewCompat to resize text. Include support libs version 26.
in your build.gradle:
compile "com.android.support:appcompat-v7:$supportLibVersion"
In your activity:
TextViewCompat.setAutoSizeTextTypeUniformWithConfiguration(textView,
textMinSizeAsAnInteger,
textMaxSizeAsAnInteger,
stepIncrement,
TypedValue.COMPLEX_UNIT_PX);