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.
Related
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
So I have a text of variable length and until now I have filled it with a SpannableString to highlight specific "hard" words you can then click on to get an explanantion in a dialog. But because I have to design my application for accessibility I neeed androids Talkback feature to read out these words (plus the text surrounding it, but I've got that for now) as well as being able to click them. So far I haven't found a way even to click on the ClickableSpan without disabling Talkback.
I found something about ClickableSpan not being able to handle Acessibility but URLSpan is? If that is the case, can I open a dialog with custom text with a URLSpan? Or does it have something to do with me calling text.setMovementMethod(LinkMovementMethod.getInstance()); ?
Thanks in advance, it's been really hard to find anything on Accessibility, not many programmers seem to care much.
Talkback users will use local context menu to access links within text: https://support.google.com/accessibility/android/answer/6007066?hl=en
Maybe this can be the easiest solution..it worked for me.
textView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
ClassroomLog.log(TAG, "Textview Click listener ");
if (textView.getSelectionStart() == -1 && textView.getSelectionEnd() == -1) {
// Perform your action here
}
}
});
So I finally did find a way to use Talkback with SpannableStrings. Well, not really, but it's a workaround. I removed the ClickableSpan from the TextView but stored the start and end positions and put the textView into another Layout.
I then iterated through the stored positions and added empty Views right on top of the text with a fitting ContentDescription and the onClick property I needed in it's onClickListener. To get the Views position and size I used this code:
//clickableSpans is an int[] Array with the start and end character positions of the previous clickableSpans stored after each other
for (int i = 0; i < clickableSpans.length; i +=2) {
int width= (int) (layout.getPrimaryHorizontal(clickableSpans[i+1]) - (int) layout.getPrimaryHorizontal(clickableSpans[i]));
int height= layout.getLineBottom(layout.getLineForOffset(clickableSpans[i])) - layout.getLineTop(layout.getLineForOffset(clickableSpans[i]));
int xOffset = (int) layout.getPrimaryHorizontal(clickableSpans[i]);
int yOffset = layout.getLineTop(layout.getLineForOffset(clickableSpans[i]));
//Now position an empty View according to those values
}
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"
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);
}
}
My TextSwitcher for each record in ListView should display first value (text1) and then another value (text2), then first value again and so on. It should happen only if text2 not empty. Otherwise text1 should be always shown (without any changes and animation).
I've created Runnable(), which changes boolean variable (time2) to then call items.notifyDataSetChanged(). It works as expected and in result setViewValue() for my ListView is called.
Here is the code:
items.setViewBinder(new SimpleCursorAdapter.ViewBinder() {
#Override
public boolean setViewValue(View view, Cursor cursor, int columnIndex) {
int viewId = view.getId();
switch(viewId) {
case R.id.timetext:
TextSwitcher itemTime = (TextSwitcher) view;
if (itemTime.getChildCount() != 2) {
itemTime.removeAllViews();
itemTime.setFactory(new ViewSwitcher.ViewFactory() {
#Override
public View makeView() {
TextView t = new TextView(MyActivity.this);
t.setTextSize(18);
t.setTypeface(null, Typeface.BOLD);
t.setTextColor(Color.WHITE);
return t;
}
});
itemTime.setAnimateFirstView(true);
itemTime.setInAnimation(AnimationUtils.loadAnimation(MyActivity.this,
R.anim.push_up_in));
itemTime.setOutAnimation(AnimationUtils.loadAnimation(MyActivity.this,
R.anim.push_up_out));
}
if (!text2.equals("")) {
if (!time2) {
itemTime.setText(text1);
} else {
itemTime.setText(text2);
}
} else {
itemTime.setCurrentText(text1);
}
return true;
}
return false;
}
} );
It works almost as expected. With one minor item - when text2 should be shown, it changes displayed value to some other value first (from another record!) and then animation is played. Change of text2 to text1 happens correctly.
My understanding that the reason is the following - before displaying text2, all views of itemTime are removed and hence it is recreated and that is why some other value is shown for a second. But why does it show value from some other record?
Actually text2 and text1 are values from the database, for ex.
text2 = cursor.getString(cursor.getColumnIndexOrThrow(DbAdapter.KEY_TIME_2)), probably, something is wrong here and setViewValue called with wrong parameters?
Upd. text1 and text2 are read from the database at setViewValue. Here is example of the full code:
itemTime.setText(cursor.getString(cursor.getColumnIndexOrThrow(DbAdapter.KEY_CLOSE_TIME_1)) + " - " + cursor.getString(cursor.getColumnIndexOrThrow(DbAdapter.KEY_OPEN_TIME_1)));
I know this might not answer the question directly, but I'm going to respond to your comment about creating a Runnable() to do the work of switching for you because I suspect that it is probably messing with your data (hard to tell when you cant see the full code).
I advise you to use a ViewFlipper instead of a TextSwitcher. The reason for doing that is that once you added the TextView's inside your ViewFlipper, you can just set your flip interval and then start the flipping and it will do it automatically for you.
As simple as this:
/* Add your items to your ViewFlipper first */
myViewFlipper.setFlipInterval(1000); //time in millseconds
myViewFlipper.startFlipping();
In your current method that you described, when you call items.notifyDataSetChanged() you incur a huge performance hit because all items of your database are going to be re-read and your list will be "re-drawn" again. You should only do that if your actual data really changed rather than using it to switch between text that you already have and doesn't change from creation time.
As a nice surprise, you might notice that your problem goes away because you don't have to re-read everything from you DB again and reduces the chances of mix-up of item1 and item2 since you will only need to read them once when the row is created in your ListView
Just my 2 cents.
Let me know how it goes.
I think I see what's going on here, and it's because of the way ListView works.
ListView recycles all of its views internally so that you only have as many views created as can be displayed on the screen. However, this also means that when you bind values to a view in your setViewValue method, you are not always given the view that was in the same position in the list before.
Say you have three list items: itemA, itemB, itemC in that order. Each contains text1, text2, and text3 respectively at first.
When you call items.notifyDataSetChanged(), ListView recycles all those list items however it feels like, so you may get a new order of itemC, itemA, itemB; and the text would then read text3, text1, text2.
As a result, when you change the text of the first list item to "text2", you will in fact see "text3" change to "text2" instead of a transition from "text1" to "text2" like you are expecting.
Are text1 and text2 stored in the resources file (res/values/strings.xml)? If so, Android will sometimes confuse variables. Simply running Project > Clean on this project may fix the problem.
This worked for me :
myViewFlipper.setFlipInterval(1000);
myViewFlipper.startFlipping();