Measure TextView height - auto wrap - android

I've created an expandable cardview, which contains a title, subtitle and a detail textview.
Now I want to get the height dynamically depending on the height of the text in a textview with wrap_content. So I used the .measure method on my view in the onClickListener:
holder.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
holder.getDetailText().measure(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
Log.i("Project", ""+ holder.getDetailText().getMeasuredHeight());
}
});
I get a height of 57 with this text:
holder.getDetailText().setText("Test Test Test Test");
If I now add a longer text in there which automatically wraps to multiple lines I get the same height of 57
holder.getDetailText().setText("Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test ...");
As soon as I put a text with new line feed in there everything works as expected which results in a height of 155
holder.getDetailText().setText("Test\nTest\nTest");
So how is it possible to get the actually height of the textview with automatically wrapped text?
Update:
This is a very long text which is automatically wrapped, but I get the height of 57, so it will only display the first line, because the getMeasuredHeight only returns this (57) height.
What I expect, but doesn't work, because the getMeasuredHeight returns the wrong height:
If I manually put \n feed, so there is no auto-wrap getMeasuredHeight returns the right height and everything works as expected.
It seems that the .measure ignores the width, because even if I put there a constant value the height returned by getMeasuredHeight wont't change.

For long texts, TextView takes a certain amount of time to render. So before that we are calculating the height of that TextView
Use the following code:
view.setText("Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test ...");
view.getViewTreeObserver()
.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
Toast.makeText(getApplicationContext(), "Height :- " + view.getMeasuredHeight(), Toast.LENGTH_SHORT).show();
}
});
Please refer to How to find the Text Area(Height/Width) of TextView programmatically in android
If you want to see exact behavior of TextView then create a CustomTextView and see how their lifecycle behaves.
Please let me know if it dosen't work for you.

I dont know whether you have figured out a solution for this,
the solution is when you are measuring height with definite width specified
you need to be very careful with the width (because the calculation is very precise) if you are not getting desired height that means you are missing something in your spec,
example is below
val widthMeasureSpec =
View.MeasureSpec.makeMeasureSpec(ceil(context.resources.getDimension(R.dimen.text_width)).toInt(), View.MeasureSpec.EXACTLY)
val heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED).
in the above code what you pass matter for the width parameter,
you need pass the exact width that you have passed for your textview
you need to pass measure spec as View.MeasureSpec.EXACTLY in my case

Related

Get Height Of View After Content Is Populated At Runtime

I have a Cardview with height WRAP_CONTENT . Content of Cardview is to be populated after network call . And based on the height of Cardview I have to set background in my fragment . So I tried using ViewTreeObserver and Post method but I am still getting value as 0.
cardTierInfo.post {
cardTierInfoHeight = cardTierInfo.height
setLayoutParams(cardTierInfoHeight)
}
Call is being made in OncreateView.
If you using kotlin extends core-ktx it is easy to get actual height by doOnLayout
cardTierInfo.doOnLayout {
cardTierInfoHeight = cardTierInfo.height
setLayoutParams(cardTierInfoHeight)
}

Fragments : what is the best place to measure Views?

I have a fragment and I need to measure location/width/height of its views on screen and pass to some other class.
So what I have is a function which does it, something like this :
private void measureTest(){
v = ourView.findViewById(R.id.someTextField);
v.getLocationOnScreen(loc);
int w = v.getWidth();
...
SomeClass.passLocation(loc,w);
...
The problem is that the location/width/height of views is not ready within fragment lifecycle.
So if I run that function within these lifecycle methods :
onCreateView
onViewCreated
onStart
onResume
I either get wrong location and width/height measurments or 0 values.
The only solution I found is to add a GlobalLayoutListener like this to mainView
mainView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
public void onGlobalLayout() {
if(alreadyMeasured)
mainView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
else
measureTest();
}
});
this gets the job done.. but its just Yack! IMO.
Is there a better way of doing this? seems like such a basic thing to do
inside onActivityCreated of your fragment retrieve the currentView (with getView()) and post a runnable to its queue. Inside the runnable invoke measureTest()
There is no better way. That code isn't that bad! It's fired as soon as the view is layed out (my terminology might be a bit weird there) which happens right after measuring. That is how it is done in the BitmapFun sample (see ImageGridFragment, line 120) in Google's Android docs. There is a comment on that particular piece of code stating:
// This listener is used to get the final width of the GridView and then calculate the
// number of columns and the width of each column. The width of each column is variable
// as the GridView has stretchMode=columnWidth. The column width is used to set the height
// of each view so we get nice square thumbnails.

TextView.setMaxLines not working?

In my app I have a screen where I display some text and then a photo. The text is variable in length (sometimes none at all, sometimes a lot), so I wanted to have it set up so the text never takes up more than a few lines (but can be scrolled) leaving enough room for the image below.
My view component for this part is created programatically, and I've adjusted the code to have the following (currently in my text-setting method, but the same thing happens if it's in the initial view-create code)
public void SetDescription(String description)
{
mTxtDescription.setText(Html.fromHtml(description));
mTxtDescription.setClickable(true);
mTxtDescription.setMaxLines(5);
mTxtDescription.setLines(5); //this makes no difference either!
mTxtDescription.setSingleLine(false);
mTxtDescription.setScrollbarFadingEnabled(true);
mTxtDescription.setScrollBarStyle(VERTICAL);
mTxtDescription.setMovementMethod(ScrollingMovementMethod.getInstance());
mTxtDescription.invalidate(); //adding this made no difference...
}
However it doesn't work- long text still fills the whole screen and the image has vanished due to being pushed down to a height of 0. How can I get the text to never be more than 5 lines?
Try removing the call to setSingleLine. And use setInputType(InputType.TYPE_TEXT_FLAG_MULTI_LINE). It'd also put this call before the setMaxLines and setLines call to be sure.
Note: setLines overrides the settings of setMaxLines and setMinLines.
The TextView has many issues surrounding the various calls to how it should display multiple, ellipses, etc.
The setSingleLine(false) seemes to reset the setMaxLines command. Try to move the setSingleLine command before the setText. That worked for me.
The below code is working fine for me
txt = (TextView)findViewById(R.id.textview);
txt.setMaxLines(5);
txt.setMovementMethod(new ScrollingMovementMethod());
txt.setScrollContainer(true);
txt.setText("Example Text");
txt.setTextColor(Color.WHITE);
txt.setScrollbarFadingEnabled(true);
in xml inside textview
android:scrollbars="vertical"

Why setting text from onMeasure does not affect TextView?

There is a TextView of a certain size and whenever text set to it is too long I'd like to re-set it to some preset value.
To accomplish this I am overriding onMeasure and calling setText. However this does not affect the TextView contents.
What am I missing?
EDIT: if you think that it should be done in a completely different way - please feel free to suggest
onMeasure() is usually called during layout phase. Besides as far as I know onMeasure() is where YOU have to measure your view. It receives 2 values which are the size of the parent of your view which may be constant.
Why don't you just check the length of the text you're setting and if it's too long just replace it with your default one?
From the View documentation page.
Measure the view and its content to determine the measured width and
the measured height. This method is invoked by measure(int, int)
and should be overriden by subclasses to provide accurate and
efficient measurement of their contents.
If you want the textview to have limited width or height call setMaxWidth() setMaxHeight() setMaxLines() or check it manualy and change is the way you like from a custom method
In your onMeasure(int,int)
CALL super(int,int)
Use getMeasuredWidth() and getMeasuredHeight
something like this:
void onMeasure(int a, int b){
super(a,b);
if(getMeasuredWidth()>bla)
setText("default");
}
This is what I do to reduce the text font in case the text content is large enough to goto the next line. The problem is pretty much the same the only difference in your case is that you want to replace it with some other default text.
ViewTreeObserver vto = ((TextView)findViewById(R.id.myTextView)).getViewTreeObserver();
vto.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
if (null != ((TextView)findViewById(R.id.myTextView))) {
if (1 < ((TextView)findViewById(R.id.myTextView)).getLineCount()) {
((TextView)findViewById(R.id.myTextView)).setTextSize(TypedValue.COMPLEX_UNIT_PX,
((TextView)findViewById(R.id.myTextView)).getTextSize() - 2);
}
}
}
});
This would take care of your length dynamically. Now the text size or length cannot be fixed as based on different form factors it would change. For instance a 20 character text in 3.2 inch screen would be decidedly large however it would be too small for 4.1 inch screen. So I would suggest you to use line count as the reference and if the text wraps then you can either reduce the font like I do or replace it by something else.
You could register a custom TextWatcher with addTextChangedListener (TextWatcher watcher).
Inside the TextWatcher you would override the afterTextChanged(Editable s) function, mesure the lenght of the text, and if too long, set it back to your default text.
EDIT: I'm not too sure about this, but this is why I think your solution with onMeasure is not working: onMeasure is called on the initial layout phase, or when the UI needs to be resized, so once your UI is set, if you change the text of the UI afterwards, and the text becomes too long, this doesn't affect the size of the TextView, and onMeasure is not called again...
AnyWay better to just look out for text changes, and do what you want when it's over a limit, and let Android do the dirty TextView measurement work
You will want to implement Textwatcher and in the onTextChanged(...) method, call TextView's setEms(...) method, which will set the width of the TextView to the width of (n m's), where n is the input for setEms(...).
...
TextView tv = (TextView) findViewById(R.id.my_text_view);
tv.addTextChangedListener(this);
...
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
tv.setEms(count);
}
This is my idea:
You can measure the text trivially with .length() if its length is bigger than your desired limit, than cut put only the substring. But if you want to display entire text you can implement onClickListener() on that TextView which envoking will show entire text as Dialog or whatever.
Another approach which I think can be suitable is to create different layouts for different density screens and then add in your xml android:maxLines="your limit" android:textSize="some value" and android:maxLength="#integer/mylimit" (different limit and size for different density). So what remains is to measure the length and only show the substring (or default text)
Something like:
String myText = "bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla";
String clickForMore="...Click for more";
int limit = myActivity.this.getResources().getInteger(R.integer.limit); //dont forget to declare it in your string.xml
Imagine we have 3 lines of text and we permitted only 2 (with total length value of int limit). Slice of our text fits inside the TextView, but it is not entirely shown. We need to inform the user that there is more to read...
myTextView.setVisibility(View.INVISIBLE);// <--- takes the space in the container
if(clickForMore.length() < myText.length() && myText.length() > limit)// <--- avoiding negative place and checking if the entire text is inside the box or there are characters leftover.
{
String temp = myText.substring(0, limit-clickForMore.length());// <-- we cutting space from currently displayed (not original string) to append the `clickForMore` string information.
myTextView.setText(temp+clickForMore);
myTextView.setVisibility(View.VISIBLE);
myTextView.setOnTouchListener(new OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
// display the dialog with full text....
return false;
}
}); //<--- instantiate the click listener
}
else
{
//the text fits the box and no need to slice it, just display it straight forward
}
myText.length() > limit - Test whether the string overlaps the limit.
Implement .addTextChangeListener() (TextWatcher) and in afterTextChange() override method implement all these I mentioned above.
Then you create onClickListener() for the TextView where you make dialog and then show your text as it is.
I hope you find this idea, reasonable. If you need more explanation what I had in mind feel free to write me.

How to disable the TextView maxLines programmatically?

I'm having a hard time reseting the maxLines attribute of a TextView programmatically.
Just tried setting to 0 and it doesn't work. -1 crashes the application. I could use a simpler workaround and set the maxLines to 5000 but I don't want to do that.
Any ideas how to do that?
UPDATED
Well, I've found one problem.. I've set the Ellipsize as well... I'm just going to use the following workaround:
TextView questionDetail = (TextView) mQuestionDetailHeader.findViewById(R.id.desc);
questionDetail.setText(mCurrentQuestion.getQuestion());
questionDetail.setMaxLines(Integer.MAX_VALUE); //As in the android sourcecode
questionDetail.setEllipsize(null);
As there isn't yet an approved answer - the proper way to reset the maxlines property of the TextView is:
textView.setMaxLines(Integer.MAX_VALUE);
As per Valdemar's comment and this stackoverflow answer. Using -1 will cause an ArrayIndexOutOfBoundsException.
Keep in mind only END and MARQEE setEllipsize() settings will be respected for maxlines >= 2 according to the documentation:
If setMaxLines(int) has been used to set two or more lines, END and
MARQUEE* are only supported (other ellipsizing types will not do
anything).
For setting the maxLines for a text use mTextView.setMaxLines(0) or you have to programmatic-ally measure of the height text and multiply with the number of max line
The result should set as the height of the textView
if you want to have just a single line , then why don't you use:
txtView.setSingleLine(true);
The -1 should not crash your application. This actually what is used inside TextView by default:
case com.android.internal.R.styleable.TextView_maxLines:
setMaxLines(a.getInt(attr, -1));
break;
This piece of code shows, that when android:maxLines is not specified, then code uses -1 to set the value via setMaxLines() function.
I also made test application to verify my conclusions. And it works fine without crashing:
public class HelloWorld extends Activity
{
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity);
TextView text = (TextView)findViewById(R.id.text);
text.setMaxLines(-1);
}
}

Categories

Resources