I needed TextView which would flash on text change - so I made CustomTextView like described in here. It works great but I have problem when I set that CustomTextView in list item. Because of ListView items are being reused my CustomTextView keeps flashing when scrolling up/down because it's context changes and now it 'points' to another list item.
Problem is I don't know how to determine when context of the item is changed, so I cannot put noFlash flag (Text property of the CustomTextView is not set to null, so I cannot use that either)
On the end I had to make custom adapter with some code in BindBindbleCode
protected override void BindBindableView(object source, IMvxListItemView viewToUse)
{
var newValue = source as ListItemVM;
var oldValue = viewToUse.DataContext as ListItemVM;
if (newValue.ItemID != oldValue.ItemID)
newValue.Rebinded = true;
base.BindBindableView(source, viewToUse);
}
then I added NotLoaded property on CustomTextView and binded those two together. So when Rebinded is true it sets NotLoaded to true signalizing that data in that TextView is not loaded. If data is not loaded there's no need to flash background of CustomTextView.
public string AnimatingText
{
get { return Text; }
set
{
if (Text == value)
return;
if (NotLoaded)
{
Text = value;
NotLoaded = false;
return;
}
Text = value;
// Do your animation here
}
}
I have TextView with width as wrap content. In this TextView I set text, but text is not of the same length every time. When text is very long I use single line true and ellipsize: end. But now I have a problem. I want to set Visibility of other layout but that depends on the length my text. If text is too long to fit in the screen I want to setVisible true, but when text is short and when I don't need ellipsize, I want to set visibility false. So I need to check status of my TextView. When its ellipsize I want to setVisible true, when its not setVisible false. How I can do that.
This is what I got:
tvAle.post(new Runnable() {
#Override
public void run() {
int lineCount = tvAle.getLineCount();
Paint paint = new Paint();
paint.setTextSize(tvAle.getTextSize());
final float size = paint.measureText(tvAle.getText().toString());
Log.v("a", ""+size+" "+tvAle.getWidth());
if ((int)size > (tvAle.getWidth()+10)) {
allergiesLayout.setVisibility(View.VISIBLE);
}
else
allergiesLayout.setVisibility(View.GONE);
}
but this solution doesn't work.
You can use this method provided: getEllipsisCount
Layout layout = textview1.getLayout();
if(layout != null) {
int lines = layout.getLineCount();
if(lines > 0) {
int ellipsisCount = layout.getEllipsisCount(lines-1);
if ( ellipsisCount > 0) {
Log.d(TAG, "Text is ellipsized");
}
}
}
where line could be obtained via getLineCount()
Well, the accepted solution does work but misses a few corner cases because it will only check the last line for ellipsized characters. If we have a TextView consisting of two lines and use TruncateAt.START to truncate the text at its beginning, the accepted answer will fail. :-/
Adding an ViewTreeObserver.OnPreDrawListener seems more like a really expensive overhead to me. So I made the following improvements to the code of the accepted answer:
/**
* Checks if the text of the supplied {#link TextView} has been ellipsized.
*
* #param textView
* The {#link TextView} to check its text.
*
* #return {#code True} if the text of the supplied {#code textView} has been ellipsized.
*/
public static boolean isTextViewEllipsized(final TextView textView) {
// Check if the supplied TextView is not null
if (textView == null) {
return false;
}
// Check if ellipsizing the text is enabled
final TextUtils.TruncateAt truncateAt = textView.getEllipsize();
if (truncateAt == null || TextUtils.TruncateAt.MARQUEE.equals(truncateAt)) {
return false;
}
// Retrieve the layout in which the text is rendered
final Layout layout = textView.getLayout();
if (layout == null) {
return false;
}
// Iterate all lines to search for ellipsized text
for (int line = 0; line < layout.getLineCount(); ++line) {
// Check if characters have been ellipsized away within this line of text
if (layout.getEllipsisCount(line) > 0) {
return true;
}
}
return false;
}
There is still room for improvement, though. But this method does suffice my use cases. Corrections and improvements are appreciated. :-)
Using getEllipsisCount wont work with text that has empty lines within it. I used the following code to make it work :
message.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
#Override
public boolean onPreDraw() {
if(m.isEllipsized == -1) {
Layout l = message.getLayout();
if (message.getLineCount() > 5) {
m.isEllipsized = 1;
message.setMaxLines(5);
return false;
} else {
m.isEllipsized = 0;
}
}
return true;
}
});
Make sure not to set a maxLineCount in your XML. Then you can check for the lineCount in your code and if it is greater than a certain number, you can return false to cancel the drawing of the TextView and set the line count as well as a flag to save whether the textView is too long or not. The textview will draw again with the correct line count and you will know whether its ellipsized or not with the flag.
You can then use the isEllipsized flag to do whatever you require.
I have an application where I need to display a list of numbers, but the numbers need to be formatted based on their value. Negative numbers are shown in normal text, positive numbers shown as bold. Also, the number needs to always appear positive in the text view. I tried extending TextView with setText overriden as such:
#Override
public void setText(CharSequence text, TextView.BufferType type) {
double number = Double.parseDouble(text.toString());
if (number > 0) {
this.setTypeface(this.getTypeface(), BOLD);
} else {
this.setTypeface(this.getTypeface(), NORMAL);
number = Math.abs(number);
}
super.setText(number + "", type);
}
This didn't quite work, as the setText was being called multiple times on the same MyTextView. This resulted in every number appearing bold, as it was positive the next time through.
I would like to keep this logic in a widget, as opposed to where the text is being set, as this is a very common occurrence in my application.
Is there a way that I can do this in a widget?
Just add a member variable to your class to check whether it was already modified or to keep the original value.
private double originalValue = 0;
#Override
public void setText(CharSequence text, TextView.BufferType type) {
if(originalValue==0) {
originalValue = Double.parseDouble(text.toString());
}
this.setTypeface(this.getTypeface(), originalValue>0 ? BOLD : NORMAL);
super.setText(Math.abs(originalValue), type);
}
Ok, I ended up just making an adapter for each list that used this special case, and took care of it in the activity for any other instance of it. Something like this:
#Override
public void bindView(View view, Context context, Cursor cursor) {
TextView text = (TextView) view.findViewById(R.id.special_text);
double amount = cursor.getDouble(cursor.getColumnIndex(DbAdapter.KEY_NUMBER));
if (amount > 0) {
amountText.setTypeface(null, Typeface.BOLD);
} else {
amountText.setTypeface(null, Typeface.NORMAL);
amount = Math.abs(amount);
}
text.setText(amount);
}
I want to make a TextView that is collapsable by user's touch.
When the TextView collapsed, I set textView.setMaxLines(4);.
How to I clear this state in my expand method?
I can only think of call setMaxLines() with a value large number like 10000.
Are there better ways to implement this?
Actually, the way android platform does that is by setting the MaxLine to Integer.MAX_VALUE.
textView.setMaxLines(Integer.MAX_VALUE);
also, if you are using Ellipsize, don't forget to set to null.
textView.setEllipsize(null);
just check how the android framework do just that ;) watch the setMaxLines(Integer.MAX_VALUE);
private void applySingleLine(boolean singleLine, boolean applyTransformation) {
mSingleLine = singleLine;
if (singleLine) {
setLines(1);
setHorizontallyScrolling(true);
if (applyTransformation) {
setTransformationMethod(SingleLineTransformationMethod.getInstance());
}
} else {
setMaxLines(Integer.MAX_VALUE);
setHorizontallyScrolling(false);
if (applyTransformation) {
setTransformationMethod(null);
}
}
}
You can find this in the source code of Android Open Source Project (AOSP)
https://source.android.com/source/downloading
If you do not want to download the source, you can view the source on a mirror like this one at github.
https://github.com/aosp-mirror/platform_frameworks_base/blob/master/core/java/android/widget/TextView.java
Try this (infoView.getLineCount()):
public void onMoreClick(View v) {
Button btn = (Button) v;
if(!moreSwitcher) {
infoView.setMaxLines(infoView.getLineCount());
infoView.setLines(infoView.getLineCount());
moreSwitcher = true;
btn.setText(R.string.collapse);
}else{
infoView.setMaxLines(5);
infoView.setLines(5);
moreSwitcher = false;
btn.setText(R.string.expand);
}
}
I have a multi-line TextView that has android:ellipsize="end" set. I would like to know, however, if the string I place in there is actually too long (so that I may make sure the full string is shown elsewhere on the page).
I could use TextView.length() and find about what the approximate length of string will fit, but since it's multiple lines, the TextView handles when to wrap, so this won't always work.
Any ideas?
You can get the layout of the TextView and check the ellipsis count per line. For an end ellipsis, it is sufficient to check the last line, like this:
Layout l = textview.getLayout();
if (l != null) {
int lines = l.getLineCount();
if (lines > 0)
if (l.getEllipsisCount(lines-1) > 0)
Log.d(TAG, "Text is ellipsized");
}
This only works after the layout phase, otherwise the returned layout will be null, so call this at an appropriate place in your code.
textView.getLayout is the way to go but the problem with that is that it returns null if layout is not prepared. Use the below solution.
ViewTreeObserver vto = textview.getViewTreeObserver();
vto.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
Layout l = textview.getLayout();
if ( l != null){
int lines = l.getLineCount();
if ( lines > 0)
if ( l.getEllipsisCount(lines-1) > 0)
Log.d(TAG, "Text is ellipsized");
}
}
});
Code snippet for removing the listener (source):
mLayout.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
public void onGlobalLayout() {
scrollToGridPos(getCenterPoint(), false);
mLayout.getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
});
I think the easiest solution to this question is the following code:
String text = "some looooong text";
textView.setText(text);
boolean isEllipsize = !((textView.getLayout().getText().toString()).equalsIgnoreCase(text));
This code assumes that in your XML the TextView set a maxLineCount :)
This worked to me:
textView.post(new Runnable() {
#Override
public void run() {
if (textView.getLineCount() > 1) {
//do something
}
}
});
The most eloquent solution I have found (in Kotlin) is to create an extension function on TextView
fun TextView.isEllipsized() = layout.text.toString() != text.toString()
This is great because it doesn't require knowing what the full string is or worrying about how many lines the TextView is using.
TextView.text is the full text that it's trying to show, whereas TextView.layout.text is what's actually shown on the screen so if they are different it must be getting ellipsized
To use it:
if (my_text_view.isEllipsized()) {
...
}
public int getEllipsisCount (int line):
Returns the number of characters to be ellipsized away, or 0 if no ellipsis is to take place.
So, simply call :
int lineCount = textview1.getLineCount();
if(textview1.getLayout().getEllipsisCount(lineCount) > 0) {
// Do anything here..
}
Since the getLayout() cant be called before the layout is set, use this:
ViewTreeObserver vto = textview.getViewTreeObserver();
vto.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
Layout l = textview.getLayout();
if ( l != null){
int lines = l.getLineCount();
if ( lines > 0)
if ( l.getEllipsisCount(lines-1) > 0)
Log.d(TAG, "Text is ellipsized");
}
}
});
And finally do not forget to remove removeOnGlobalLayoutListener when you need it nomore.
lateinit var toggleMoreButton: Runnable
toggleMoreButton = Runnable {
if(reviewTextView.layout == null) { // wait while layout become available
reviewTextView.post(toggleMoreButton)
return#Runnable
}
readMoreButton.visibility = if(reviewTextView.layout.text.toString() != comment) View.VISIBLE else View.GONE
}
reviewTextView.post(toggleMoreButton)
It is some typical case:
comment in 'reviewTextView'
comment can collapsed by some criteria
if comment collapsed you show button 'readMoreButton'
The Kotlin way:
textView.post {
if (textView.lineCount > MAX_LINES_COLLAPSED) {
// text is not fully displayed
}
}
Actually View.post() is executed after the view has been rendered and will run the function provided
Simple Kotlin method. Allows android:ellipsize and android:maxLines to be used
fun isEllipsized(textView: TextView, text: String?) = textView.layout.text.toString() != text
Solution with kotlin extensions:
infoText.afterLayoutConfiguration {
val hasEllipsize = infoText.hasEllipsize()
...
}
Extensions:
/**
* Function for detect when layout completely configure.
*/
fun View.afterLayoutConfiguration(func: () -> Unit) {
viewTreeObserver?.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
override fun onGlobalLayout() {
viewTreeObserver?.removeOnGlobalLayoutListener(this)
func()
}
})
}
fun TextView.hasEllipsize(): Boolean = layout.getEllipsisCount(lineCount - 1) > 0
it is working for me
if (l != null) {
int lines = l.getLineCount();
if (lines > 0) {
for (int i = 0; i < lines; i++) {
if (l.getEllipsisCount(i) > 0) {
ellipsize = true;
break;
}
}
}
}
If your textview contains multiple paragraphs, using getEllipsisCount will not work for empty lines within it. getEllipsisCount for the last line of any paragraph will return 0.
Really work so, for example, to pass full data to dialog from item of RecyclerView:
holder.subInfo.post(new Runnable() {
#Override
public void run() {
Layout l = holder.subInfo.getLayout();
if (l != null) {
final int count = l.getLineCount();
if (count >= 3) {
holder.subInfo.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
final int c = holder.subInfo.getLineCount();
if (c >= 3) {
onClickToShowInfoDialog.showDialog(holder.title.getText().toString(), holder.subInfo.getText().toString());
}
}
});
}
}
}
});
Combining #Thorstenvv awnser with #Tiano fix, here is the Kotlin version :
val layout = textView.layout ?: return#doOnLayout
val lines = layout.lineCount
val hasLine = lines > 0
val hasEllipsis = ((lines - 1) downTo 0).any { layout.getEllipsisCount(it) > 0 }
if (hasLine && hasEllipsis) {
// Text is ellipsized
}
In Kotlin, you can use the below code.
var str= "Kotlin is one of the best languages."
textView.text=str
textView.post {
val isEllipsize: Boolean = !textView.layout.text.toString().equals(str)
if (isEllipsize) {
holder.itemView.tv_viewMore.visibility = View.VISIBLE
} else {
holder.itemView.tv_viewMore.visibility = View.GONE
}
}
This is simple library for creating textview expandable. Like Continue or Less. This library extended version TextView. Easy to use.
implementation 'com.github.mahimrocky:ShowMoreText:1.0.2'
Like this,
1 https://raw.githubusercontent.com/mahimrocky/ShowMoreText/master/screenshot1.png
2 https://raw.githubusercontent.com/mahimrocky/ShowMoreText/master/screenshot2.png
<com.skyhope.showmoretextview.ShowMoreTextView
android:id="#+id/text_view_show_more"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="#string/text"
/>
In Activity you can use like:
ShowMoreTextView textView = findViewById(R.id.text_view_show_more);
//You have to use following one of method
// For using character length
textView.setShowingChar(numberOfCharacter);
//number of line you want to short
textView.setShowingLine(numberOfLine);
After researching I found the best way for me in Kotlin
To get the ellipsize status the textView must be rendered first, so we have to set the text first, then check the ellipsize logic inside textView.post scope
textView.text = "your text"
textView.post {
var ellipsized: Boolean = textView.layout.text.toString()).equalsIgnoreCase("your text"))
if(ellipsized){
//your logic goes below
}
}
Using getEllipsisCount won't work with text that has empty lines within it. I used the following code to make it work :
message.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
#Override
public boolean onPreDraw() {
if(m.isEllipsized == -1) {
Layout l = message.getLayout();
if (message.getLineCount() > 5) {
m.isEllipsized = 1;
message.setMaxLines(5);
return false;
} else {
m.isEllipsized = 0;
}
}
return true;
}
});
Make sure not to set a maxLineCount in your XML. Then you can check for the lineCount in your code and if it is greater than a certain number, you can return false to cancel the drawing of the TextView and set the line count as well as a flag to save whether the text view is too long or not. The text view will draw again with the correct line count and you will know whether its ellipsized or not with the flag.
You can then use the isEllipsized flag to do whatever you require.
create a method inside your TextViewUtils class
public static boolean isEllipsized(String newValue, String oldValue) {
return !((newValue).equals(oldValue));
}
call this method when it's required eg:
if (TextViewUtils.isEllipsized(textviewDescription.getLayout().getText().toString(), yourModelObject.getDescription()))
holder.viewMore.setVisibility(View.VISIBLE);//show view more option
else
holder.viewMore.setVisibility(View.GONE);//hide
but textView.getLayout() can't call before the view(layout) set.