I am trying to calculate the height needed by a text programmatically, so I am using StaticLayout.
I was experimenting with a Label (TextView) using its paint object, to make sure the height calculated is the same as TextView's height rendered by Android.
It works good for single-line text both in Arabic and English, and works for mutli-line text in English, but Arabic height is off, it is always shorter than what Android renders.
The top height ~80 is what the TextView's height is, while the latter ~75 is the calculated height needed.
Since that it works fine in English, I guess I might need to set some other property or something, but I cannot tell what.
Following is my code, it is in C# since I am using Xamarin, but it should be clear for Android developers.
Control is TextView.
double getTextHeight(string str, double widthConstraint)
{
if (Control == null || String.IsNullOrWhiteSpace(str))
return 0;
var builder = StaticLayout.Builder.Obtain(str, 0, str.Length, Control.Paint, (int)widthConstraint);
builder.SetAlignment(Control.Layout.GetAlignment());
builder.SetIncludePad(true);
builder.SetLineSpacing(Control.Layout.SpacingAdd, Control.Layout.SpacingMultiplier);
builder.SetBreakStrategy(Control.BreakStrategy);
builder.SetJustificationMode(Control.JustificationMode);
builder.SetHyphenationFrequency(Control.HyphenationFrequency);
var staticLayout = builder.Build();
return staticLayout.Height;
}
This is the code I was missing, it is working like charm :)
builder.SetUseLineSpacingFromFallbacks(true);
builder.SetUseLineSpacingFromFallbacks(true);
but is need api 28
Related
I am building a reader app and have a custom class that expands a TextView with code to deal with pagination etc. The text is set from an Html format using
Html.fromHtml(text, Html.FROM_HTML_MODE_COMPACT, HtmlImageGetter(this, binding.textSurface, width), HtmlTagHandler(this))
It has a lot of functionalities for changing lineheights, font sizes, word spacing, etc on the fly. The layout is built with this code:
#Suppress("DEPRECATION")
private fun from(layout: Layout) : Layout =
if(Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
var tmpText = if(isUppercase) TextSegmentationNew.covertToUppercase(SpannedString(originalText)) else originalText
val formattedText = TextSegmentationNew.formatWithWordSpacing(tmpText, wordSpacing)
StaticLayout(
formattedText,
paint,
layout.width,
layout.alignment,
lineSpacingMultiplier,
lineSpacingExtra,
includeFontPadding)
} else {
var tmpText = if(isUppercase) TextSegmentationNew.covertToUppercase(SpannedString(originalText)) else originalText
val formattedText = TextSegmentationNew.formatWithWordSpacing(tmpText, wordSpacing)
StaticLayout.Builder
.obtain(formattedText, 0, formattedText.length, paint, layout.width)
.setAlignment(layout.alignment)
.setLineSpacing(lineSpacingExtra, lineSpacingMultiplier)
.setIncludePad(includeFontPadding)
.setUseLineSpacingFromFallbacks()
.setBreakStrategy(breakStrategy)
.setHyphenationFrequency(hyphenationFrequency)
.setJustificationMode()
.setMaxLines(maxLines)
.build()
}
One of the functionalities is also custom coloring of the text. So for example we could color the letter a/A in a different color. I am doing this with ForegroundColorSpan which works great. The problem I have is when I try to use a StyleSpan with a BOLD typeface on a letter.
This is a text without bolding. I am drawing the top, bottom, and baseline of every line to better show the effect:
Correct line-height when no bolding span is present
And this is what I get when I try to bold the letter A:
Wrong line-height on some lines
The TextView shrinks the line-height on the lines the bold letters are on, but ONLY on the lines where there is also a new line \n character present(the red dots on the image shows these).
I tested on the devices I have here and on android 7 it works correctly but the wrong line heights show on android 8.1 and android 11. Maybe they changed something on Android 8 but I can't figure out what...
Is there a way to force the use of the correct line-height on all lines consistently no matter what spans I use?
I have a TextView, and i want to know, at runtime, if the text is to long.
The requirement that is making this tricky, and thus haven't found a solution to it, is that I DO NOT want to use ellipsize, because I do NOT want to show three dots at the end.
ideas?
thanks
to check on runtime you have to use a paint object.
doc ref: Paint.measureText(String) and TextView.getPaint()
// the code below must be run AFTER the TextView have been layout on the screen.
Paint p = textView.getPaint();
float width = p.measureText("your text here");
if(width > textView.getWidth()){
// bigger
}
TextUtils class also have some interesting methods that you might be interested, for example, the method that calculates the ellipsize: TextUtils.ellipsize(...)
I am using ListBox with dynamically creatable ListBoxItems (Wrap enabled) to display some text. I have a problem to calculate Heigth property of ListBoxItem in code to make sure my string are fully visible in ListBoxItem rectangle. Now I am doing it like this:
I am put Label in form (opacy = 0) with autosize = true (a call it measure_Lbl), then I am prepared to create ListBoxtem, I put my text into this Label and read Width property. Then I am divide it by ListBowItem.Width and find some approximate value (may be bigger may be less). But I think there is a better way to do so...?
I have an Android application layout which contains a multiline TextView. When the screen is in portrait orientation, the TextView can display a different length of text to when running in landscape mode. Also when running on a small screen, the TextView can display a different length of text to when running on a larger screen.
Is there any way I can check if the text fits or will be truncated? Or is there any way I can check if the TextView if full?
The problem is the TextView can potentially contain a different number of lines, depending on whether it is landscape, portrait, small screen, large screen, etc.
Thank you for your advice,
Best regards,
James
These answers didn't work very well for me. Here's what I ended up doing
Paint measurePaint = new Paint(myTextView.getPaint());
float pWidth = measurePaint.measureText("This is my big long line of text. Will it fit in here?");
float labelWidth = myTextView.getWidth();
int maxLines = myTextView.getMaxLines();
while (labelWidth > 0 && pWidth/maxLines > labelWidth-20) {
float textSize = measurePaint.getTextSize();
measurePaint.setTextSize(textSize-1);
pWidth = measurePaint.measureText("This is my big long line of text. Will it fit in here?");
if (textSize < TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_SP, 7,
getContext().getResources().getDisplayMetrics())) break;
}
myTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX, measurePaint.getTextSize());
I'm not saying this will work for every situation as I'm certainly cutting corners here, but the general idea is to measure the text with the textview's paint and keep shrinking it until it will fit inside the textview.
I have found a "cheeky" solution to the problem of measuring the height of the text in a MULTILINE TextView :-
//Calculate the height of the text in the MULTILINE TextView
int textHeight = textView.getLineCount() * textView.getLineHeight();
if (textHeight > textViewHeight) {
//Text is truncated because text height is taller than TextView height
} else {
//Text not truncated because text height not taller than TextView height
}
However this solution has some caveats :-
Firstly regarding getLineHeight() , markup within the text can cause individual lines to be taller or shorter than this height, and the layout may contain additional first- or last-line padding. See http://developer.android.com/reference/android/widget/TextView.html#getLineHeight()
Secondly , the application needs to calculate the actual height of the TextView in pixels , and (in the case of my layout) it might not be as simple as textView.getHeight() , and calculation may vary from one layout to another layout.
I would recommend avoiding LinearLayout because the actual pixel height of the TextView can vary depending on text content. I am using a RelativeLayout (see http://pastebin.com/KPzw5LYd).
Using this RelativeLayout, I can calculate my TextView height as follows :-
//Calculate the height of the TextView for layout "http://pastebin.com/KPzw5LYd"
int textViewHeight = layout1.getHeight() - button1.getHeight() - button2.getHeight();
Hope that helps,
Regards,
James
Basically, you need to calculate the size of your textview and the size of your text when the orientation mode changed. Try ViewTreeObserver.OnGlobalLayoutListener to do so.
Inside the change orientation method:
main_view.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
main_view.getViewTreeObserver().removeGlobalOnLayoutListener(this);
//Calculation goes here
int text_size = getTextSize(text_view.getText().toString());
int text_view_size = text_view.getLayoutParams().width;
//Compare your text_size and text_view_size and do whatever you want here.
}
});
Here is the code of calculate the text_size:
private int getTextSize(String your_text){
Paint p = new Paint();
//Calculate the text size in pixel
return p.measureText(your_text);
}
Hope this help.
For checking whether a multiline (or not) TextView is going to be truncated, check this post.
Or, have you looked into using a scrolling textview? (marquee).. where the text will scroll by (horizontally, animated) if it is too long for a given width?
Here is an example TextView in a layout file, that has some of these characteristics:
<TextView
android:id="#+id/sometextview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="marquee"
android:marqueeRepeatLimit="marquee_forever"
android:singleLine="true"
android:scrollHorizontally="true"
android:focusable="true"
android:focusableInTouchMode="true"
android:freezesText="true"
android:textColor="#808080"
android:textSize="14sp"
android:text="This is a long scrolling line of text.. (etc)"/>
This extension function in Kotlin worked for me, just make sure to call it when the view is already laied out, e.g. view.post() is a good place for me;
/**
* Make sure to call this method only when view layout is finished, e.g. view.post() is a good place to check this
*/
fun TextView.isTruncated() = (lineCount * lineHeight) > height
Usage
textView.post {
if(isTruncated()) {
// Do something
}
}
Just check textView.getLineCount(), if line count > 1 then your text is multiline
I have a problem placing a textView at specified center's x and y coordinates.
Firstly, I tried to set the text in the textView, and to move the view with the width and the height of the view
from this link.
But it doesn't work, and I'd like to try something else.
I'd like to know if there is a method to get the size which a specified text will take in my textView?
I mean, I know the text and the textSize, how can I get the width and the height my textView will take?
Something like the method (NSString)sizeWithFont; for those who know iPhone dev.
Rect bounds = new Rect();
textView.getPaint().getTextBounds(textView.getText().toString(), 0, textView.getText().length(), bounds);
bounds.width() will give you the accurate width of the text in the Text View.
If your textview is called tv
tv.setText("bla");
tv.measure(0, 0); //must call measure!
tv.getMeasuredHeight(); //get height
tv.getMeasuredWidth(); //get width
More on this (updated): How to get width/height of a View
For some reason the solution of Midverse Engineer does not give me always correct results (at least on some Android versions). The solution of Sherif elKhatib works, but has the side effect of changing MeasuredWidth and Height. This could lead to incorrect positioning of the textView.
My solution:
width = textView.getPaint().measureText(text);
metrics = textView.getPaint().getFontMetrics();
height = metrics.bottom - metrics.top;
Using the TextView inner Paing class is not so hot when you have multiple lines of text and different paddings. So stop trying to reinvent the wheel. Use getMeasuredHeight and getMeasuredWidth methods after calling measure(UNSPECIFIED, UNSPECIFIED). Just don't forget to get the new values inside the post, otherwise mostly you'll get a wrong result.
tv.setText(state.s);
tv.measure(UNSPECIFIED,UNSPECIFIED);
tv.post(new Runnable() {
#Override
public void run() {
Log.d("tv","Height = "+tv.getMeasuredHeight());
Log.d("tv","Width = "+tv.getMeasuredWidth());
}
});