Android: how to set text depending on available space - android

I would like to be able to set the text in a TextView depending on the available space, in order to avoid ellipsizing.
For example:
if there is enough space set the text "The red fox jumps"
if there is not enough space (and consequently "The red fox jumps" would be ellipsized) set the text "jumps"
Please how can I achive that?

You could use Paint.measureText(String) to determine the width of the whole string, when drawn with your Paint object. If that value is greater than the TextView's width, then we know that the text will be ellipsised.
float totalLength = myPaint.measureText("The red fox jumps");
float tvWidth = myTextView.getWidth(); // get current width of TextView
if (tvWidth < totalLength) {
// TextView will display text with an ellipsis
}
Once we know the text will be truncated, we can use trial and error to determine what the minimum text that can be displayed on screen is. This step will depend on your business logic, but should use the same Paint calculations as the first step.
calculateStringWidth("The red fox jumps"); // too large
calculateStringWidth("red fox jumps"); // still too large
calculateStringWidth("fox jumps"); // width is less than TextView, will fit without ellipsis

One method would be to calculate the neede size for given text.
textView.setText("The red fox jumps");
// call measure is important here
textView.measure(0, 0);
int height = textView.getMeasuredHeight();
int width = textView.getMeasuredWidth();
if (height > availableHeight || width > availableWidth) {
textView.setText("jumps");
}
The call of measure() "determines the size requirements for this view and all of its children". Referring to Androids View doc. Documentation

Related

How to fit text into StaticLayout with fixed width and height in Android?

I'm trying to draw text using Canvas and have found that using StaticLayout would take care of the line breaks automatically. I also want to limit its height so that when text is too long it would be ellipsized, but the size of text container is dynamic. I can easily apply the width to StaticLayout, but cant find a way to do height.
I tried to utilize TextUtils.ellipsize(), but having issue to get the spacing between lines.
PerracoLabs has the right answer but, as CheokYanCheng stated, the calculation of the maximum number of lines is off (although it may yield the correct result many if not most of the time).
A maximum height cannot be specified for a StaticLayout except indirectly by specifying the maximum number of lines. Ellipsis is tied to the maximum line count anyway, so determining the maximum number of lines for a specific height to back into a solution is the way to go. So, how do we determine the appropriate maximum line count so that a fixed-size StaticLayout with ellipsis can be created as PerracoLabs has explained.?
If the text has no spans that effect the height any of the lines of text then a simply calculation can determine the maximum number of lines that will fit into a StaticLayout before ellipsis.
The following Kotlin function will determine how many lines of text will fit into a view that has a fixed height and width. It is assumed that each line of a StaticLayout has a set height (no height-effecting spans). The top line has the same height of other lines but it is augmented by a top padding. The bottom line has a bottom padding added to it.
private fun getMaxLines(maxHeight: Int): Int {
// Build a dummy StaticLayout to get the internal measurements.
return makeStaticLayout("", width, 1).run {
val lineHeight = getLineBottom(0) - getLineTop(0) + topPadding - bottomPadding
(maxHeight - topPadding - bottomPadding) / lineHeight
}
However, if the text contains a span that changes the height of one or more lines then the only way to calculate the maximum number of lines is through the creation of the static layout that holds the entire text (no ellipsis) followed by an inspection of the lines within the layout to determine how many complete lines have fit. A new StaticLayout can then be created with the calculated maximum lines determined from the inspection.
The following Kotlin function will calculate the maximum lines by inspecting the StaticLayout for the last full line that is present.
private fun getMaxLinesByInspection(staticLayout: StaticLayout, maxHeight: Int): Int {
var line = staticLayout.lineCount - 1
while (line >= 0 && staticLayout.getLineBottom(line) >= maxHeight) {
line--
}
return line + 1
}
I have posted a small project on GitHub as a demonstration.
Here is a screen shot of the app.
You (and #Cheok Yan Cheng) might try to make use of PagedTextView. The view is intended for Paginating text in Android.
The view partially solves the problem, i.e. reacts to dynamic size changes. As to text ellipsizing, you might achieve this by customising the algorithm I've used for measuring text in height.

How can I get the size in pixels of the space below the text of a textview? (not the bottom padding)

bottomPadding returns 0 and is set to 0, but my textview still has padding below it's text.
I am trying to draw text on a canvas at the same height as the textview is from its parent but for some reason Paint.drawText() ignores the extra padding at the bottom.
Since the canvas object has its origin a the bottom-left corner, I have to provide the the y position of the text from below.
I can't do the following:
yPosition = textview.bottom + ((rectbounds.getHeight() - textViewHeigth)/2)
This is because the size of the space on top of the text is bigger than the bottom space.
Removing the extra space from the textview would also solve my problem. However, I already tried setting includeFontPadding to false, and setting padding to 0, but neither work.
What happens if you put a 'g', 'p', 'y', or 'j' in your TextView? Each of these characters has a descender, and the metrics for the font will always allocate space for this feature of many scripts. This is to allow for the fact that multiple lines of flowing text ought not be crammed into each other.
If you really need to get the size of a font's descender, you can use a Paint object, load it with a Typeface and ask for its descent(). But a TextView I don't think is going to change the way it renders its lines for you. If you must ignore the descent, draw your own text in a custom view.

Android multiline TextView , check if text fits , or check is TextView is full

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

Android TextView getTextSize() does it include descenders, ascenders

I can't find anywhere there is a mention of how the getTextSize() in Textview is measured. From visual tests it doesn't seem to be including descenders, but seems to include ascenders. It doesn't appear to start exactly from the baseline also.
http://en.wikipedia.org/wiki/Descender
This is the closest mention of it but Romain Guy from Google just ignores that part of the question.
http://www.mail-archive.com/android-developers#googlegroups.com/msg08514.html
As I need this as I am using Compound Drawables and I need to be able to align the drawable to the text on different devices.
Here is code I used to test on a compound drawable a circle that touches edges
tvData.setTextSize(TypedValue.COMPLEX_UNIT_PX, 100);
tvData.setText("agB5ãÂ");
int size = (int)tvData.getTextSize();
Drawable img = getResources().getDrawable(R.drawable.circle_white_full_72 ).mutate();
img.setBounds( 0 , 0 , size , size);
tvData.setCompoundDrawables( null, null, img, null );
Here is the result
as you can see it doesn't seem to use the descenders and ascenders.
Here is the drawable image, if others want to test
http://i.imgur.com/Yhf8b.png
When changing the image to 80% of the text size using
int size = (int)tvData.getTextSize() *80/100;
Here is the result, with the image transposed on top of the 100% image. Maybe setCompoundrawables is doing it's own scaling
I tried measuring midpoints of the font and drawable and it is off. Here is an image highlighting it
Finally I moved the drawable 50pixels to the left, and then measured the output and it was half the height of the font text baseline to baseline, as the setTextSize was set at 100px.
Android must be using some other layout to scale and position the compound drawable. Maybe I should create another question for this.
Here is an image highlighting baseline to baseline.
From some light testing, it appears to be from ascent to descent (text size = descent - ascent). Did some debugging with a TextPaint to verify, and just to be a little more specific, I did:
Paint.FontMetricsInt metrics;
for(int i = 1; i < 100; i++) {
mTextPaint.setTextSize(i);
metrics = mTextPaint.getFontMetricsInt();
if((metrics.descent - metrics.ascent) != i) Log.v("type", "Not equal");
}
And it remained true for each value.

Can I know how many characters can fit into TextView of X dp width?

Is there a way to know how many characters of font size 10sp can fit into a TextView of a fixed width (let's say 100dp)?
I need to track if the whole string will fit (be visible) into a TextView of predefined width.
I haven't been able to find a way to track or calculate this.
Since Android uses proportional fonts, each character occupies a different width. Also if you take kerning into account the width of a string may be shorter than the sum of the widths of individual characters.
So it's easiest to measure the whole string by adding one character at a time until (a) the entire string if found to fit within the limit, or (b) the width of the string exceeds the limit.
The following code shows how to find how many characters of size 11px fits into TextView 100px wide. (You can find formulas to convert between px & dp on the Web).
We'll do it by using measureText(String text, int start, int end) in a loop incrementing the value of end until it it no longer fits the TextView's width.
String text = "This is my string";
int textViewWidth = 100;
int numChars;
Paint paint = textView.getPaint();
for (numChars = 1; numChars <= text.length; ++numChars) {
if (paint.measureText(text, 0, numChars) > textViewWidth) {
break;
}
}
Log.d("tag", "Number of characters that fit = " + (numChars - 1));
If performance is poor, you may try using a binary search method instead of a linear loop.
The best way to determine this would just be to test it. Since you are using DP it will be relatively device-independent. Unless you are using a fixed-width font there isn't really a way to determine it theoretically unless you want to actually try and measure the width of each letter, the kerning, etc. and compare to the width of the TextView

Categories

Resources