The Android TextView widget is used to display text in your application. When you have a text that's longer than one line, then the TextView will automatically put the text in multiple lines.
Android Ellipsize Android TextView ellipsize property Causes words in the text that are longer than the view's width to be ellipsized ( means to shorten text using an ellipsis, i.e. three dots …) instead of broken in the middle to fit it inside the given view.
If you want a horizontal scrollable text in your app, use android:ellipsize="marquee" where a single line large text will be scrolling.
Here is a solution to the problem. It is a subclass of TextView that actually works for ellipsizing. The android-textview-multiline-ellipse code listed in an earlier answer I have found to be buggy in certain circumstances, as well as being under GPL, which doesn't really work for most of us. Feel free to use this code freely and without attribution, or under the Apache license if you would prefer. Note that there is a listener to notify you when the text becomes ellipsized, which I found quite useful myself.
import java.util.ArrayList;
import java.util.List;
import android.content.Context;
import android.graphics.Canvas;
import android.text.Layout;
import android.text.Layout.Alignment;
import android.text.StaticLayout;
import android.text.TextUtils.TruncateAt;
import android.util.AttributeSet;
import android.widget.TextView;
public class EllipsizingTextView extends TextView {
private static final String ELLIPSIS = "...";
public interface EllipsizeListener {
void ellipsizeStateChanged(boolean ellipsized);
}
private final List<EllipsizeListener> ellipsizeListeners = new ArrayList<EllipsizeListener>();
private boolean isEllipsized;
private boolean isStale;
private boolean programmaticChange;
private String fullText;
private int maxLines = -1;
private float lineSpacingMultiplier = 1.0f;
private float lineAdditionalVerticalPadding = 0.0f;
public EllipsizingTextView(Context context) {
super(context);
}
public EllipsizingTextView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public EllipsizingTextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public void addEllipsizeListener(EllipsizeListener listener) {
if (listener == null) {
throw new NullPointerException();
}
ellipsizeListeners.add(listener);
}
public void removeEllipsizeListener(EllipsizeListener listener) {
ellipsizeListeners.remove(listener);
}
public boolean isEllipsized() {
return isEllipsized;
}
@Override
public void setMaxLines(int maxLines) {
super.setMaxLines(maxLines);
this.maxLines = maxLines;
isStale = true;
}
public int getMaxLines() {
return maxLines;
}
@Override
public void setLineSpacing(float add, float mult) {
this.lineAdditionalVerticalPadding = add;
this.lineSpacingMultiplier = mult;
super.setLineSpacing(add, mult);
}
@Override
protected void onTextChanged(CharSequence text, int start, int before, int after) {
super.onTextChanged(text, start, before, after);
if (!programmaticChange) {
fullText = text.toString();
isStale = true;
}
}
@Override
protected void onDraw(Canvas canvas) {
if (isStale) {
super.setEllipsize(null);
resetText();
}
super.onDraw(canvas);
}
private void resetText() {
int maxLines = getMaxLines();
String workingText = fullText;
boolean ellipsized = false;
if (maxLines != -1) {
Layout layout = createWorkingLayout(workingText);
if (layout.getLineCount() > maxLines) {
workingText = fullText.substring(0, layout.getLineEnd(maxLines - 1)).trim();
while (createWorkingLayout(workingText + ELLIPSIS).getLineCount() > maxLines) {
int lastSpace = workingText.lastIndexOf(' ');
if (lastSpace == -1) {
break;
}
workingText = workingText.substring(0, lastSpace);
}
workingText = workingText + ELLIPSIS;
ellipsized = true;
}
}
if (!workingText.equals(getText())) {
programmaticChange = true;
try {
setText(workingText);
} finally {
programmaticChange = false;
}
}
isStale = false;
if (ellipsized != isEllipsized) {
isEllipsized = ellipsized;
for (EllipsizeListener listener : ellipsizeListeners) {
listener.ellipsizeStateChanged(ellipsized);
}
}
}
private Layout createWorkingLayout(String workingText) {
return new StaticLayout(workingText, getPaint(), getWidth() - getPaddingLeft() - getPaddingRight(),
Alignment.ALIGN_NORMAL, lineSpacingMultiplier, lineAdditionalVerticalPadding, false);
}
@Override
public void setEllipsize(TruncateAt where) {
// Ellipsize settings are not respected
}
}
In my app, I had similar problem: 2 line of string and, eventually, add "..." if the string was too long. I used this code in xml file into textview tag:
android:maxLines="2"
android:ellipsize="end"
android:singleLine="false"
Try this, it works for me, I have 4 lines and it adds the "..." to the end of the last/fourth line. Its the same as morale's answer but i have singeLine="false" in there.
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:maxLines="4"
android:ellipsize="marquee"
android:singleLine="false"
android:text="Hi make this a very long string that wraps at least 4 lines, seriously make it really really long so it gets cut off at the fourth line not joke. Just do it!" />
I've run into this problem, too. There's a rather old bug about it that remains unanswered: Bug 2254
I combined the solutions by Micah Hainline, Alex Băluț, and Paul Imhoff to create an ellipsizing multiline TextView
that also supports Spanned
text.
You only need to set android:ellipsize
and android:maxLines
.
/*
* Copyright (C) 2011 Micah Hainline
* Copyright (C) 2012 Triposo
* Copyright (C) 2013 Paul Imhoff
* Copyright (C) 2014 Shahin Yousefi
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.support.annotation.NonNull;
import android.text.Layout;
import android.text.Layout.Alignment;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.StaticLayout;
import android.text.TextUtils;
import android.text.TextUtils.TruncateAt;
import android.util.AttributeSet;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
public class EllipsizingTextView extends TextView {
private static final CharSequence ELLIPSIS = "\u2026";
private static final Pattern DEFAULT_END_PUNCTUATION
= Pattern.compile("[\\.!?,;:\u2026]*$", Pattern.DOTALL);
private final List<EllipsizeListener> mEllipsizeListeners = new ArrayList<>();
private EllipsizeStrategy mEllipsizeStrategy;
private boolean isEllipsized;
private boolean isStale;
private boolean programmaticChange;
private CharSequence mFullText;
private int mMaxLines;
private float mLineSpacingMult = 1.0f;
private float mLineAddVertPad = 0.0f;
private Pattern mEndPunctPattern;
public EllipsizingTextView(Context context) {
this(context, null);
}
public EllipsizingTextView(Context context, AttributeSet attrs) {
this(context, attrs, android.R.attr.textViewStyle);
}
public EllipsizingTextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
TypedArray a = context.obtainStyledAttributes(attrs,
new int[]{ android.R.attr.maxLines }, defStyle, 0);
setMaxLines(a.getInt(0, Integer.MAX_VALUE));
a.recycle();
setEndPunctuationPattern(DEFAULT_END_PUNCTUATION);
}
public void setEndPunctuationPattern(Pattern pattern) {
mEndPunctPattern = pattern;
}
public void addEllipsizeListener(@NonNull EllipsizeListener listener) {
mEllipsizeListeners.add(listener);
}
public void removeEllipsizeListener(EllipsizeListener listener) {
mEllipsizeListeners.remove(listener);
}
public boolean isEllipsized() {
return isEllipsized;
}
@SuppressLint("Override")
public int getMaxLines() {
return mMaxLines;
}
@Override
public void setMaxLines(int maxLines) {
super.setMaxLines(maxLines);
mMaxLines = maxLines;
isStale = true;
}
public boolean ellipsizingLastFullyVisibleLine() {
return mMaxLines == Integer.MAX_VALUE;
}
@Override
public void setLineSpacing(float add, float mult) {
mLineAddVertPad = add;
mLineSpacingMult = mult;
super.setLineSpacing(add, mult);
}
@Override
public void setText(CharSequence text, BufferType type) {
if (!programmaticChange) {
mFullText = text;
isStale = true;
}
super.setText(text, type);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
if (ellipsizingLastFullyVisibleLine()) isStale = true;
}
@Override
public void setPadding(int left, int top, int right, int bottom) {
super.setPadding(left, top, right, bottom);
if (ellipsizingLastFullyVisibleLine()) isStale = true;
}
@Override
protected void onDraw(@NonNull Canvas canvas) {
if (isStale) resetText();
super.onDraw(canvas);
}
private void resetText() {
int maxLines = getMaxLines();
CharSequence workingText = mFullText;
boolean ellipsized = false;
if (maxLines != -1) {
if (mEllipsizeStrategy == null) setEllipsize(null);
workingText = mEllipsizeStrategy.processText(mFullText);
ellipsized = !mEllipsizeStrategy.isInLayout(mFullText);
}
if (!workingText.equals(getText())) {
programmaticChange = true;
try {
setText(workingText);
} finally {
programmaticChange = false;
}
}
isStale = false;
if (ellipsized != isEllipsized) {
isEllipsized = ellipsized;
for (EllipsizeListener listener : mEllipsizeListeners) {
listener.ellipsizeStateChanged(ellipsized);
}
}
}
@Override
public void setEllipsize(TruncateAt where) {
if (where == null) {
mEllipsizeStrategy = new EllipsizeNoneStrategy();
return;
}
switch (where) {
case END:
mEllipsizeStrategy = new EllipsizeEndStrategy();
break;
case START:
mEllipsizeStrategy = new EllipsizeStartStrategy();
break;
case MIDDLE:
mEllipsizeStrategy = new EllipsizeMiddleStrategy();
break;
case MARQUEE:
super.setEllipsize(where);
isStale = false;
default:
mEllipsizeStrategy = new EllipsizeNoneStrategy();
break;
}
}
public interface EllipsizeListener {
void ellipsizeStateChanged(boolean ellipsized);
}
private abstract class EllipsizeStrategy {
public CharSequence processText(CharSequence text) {
return !isInLayout(text) ? createEllipsizedText(text) : text;
}
public boolean isInLayout(CharSequence text) {
Layout layout = createWorkingLayout(text);
return layout.getLineCount() <= getLinesCount();
}
protected Layout createWorkingLayout(CharSequence workingText) {
return new StaticLayout(workingText, getPaint(),
getMeasuredWidth() - getPaddingLeft() - getPaddingRight(),
Alignment.ALIGN_NORMAL, mLineSpacingMult,
mLineAddVertPad, false /* includepad */);
}
protected int getLinesCount() {
if (ellipsizingLastFullyVisibleLine()) {
int fullyVisibleLinesCount = getFullyVisibleLinesCount();
return fullyVisibleLinesCount == -1 ? 1 : fullyVisibleLinesCount;
} else {
return mMaxLines;
}
}
protected int getFullyVisibleLinesCount() {
Layout layout = createWorkingLayout("");
int height = getHeight() - getCompoundPaddingTop() - getCompoundPaddingBottom();
int lineHeight = layout.getLineBottom(0);
return height / lineHeight;
}
protected abstract CharSequence createEllipsizedText(CharSequence fullText);
}
private class EllipsizeNoneStrategy extends EllipsizeStrategy {
@Override
protected CharSequence createEllipsizedText(CharSequence fullText) {
return fullText;
}
}
private class EllipsizeEndStrategy extends EllipsizeStrategy {
@Override
protected CharSequence createEllipsizedText(CharSequence fullText) {
Layout layout = createWorkingLayout(fullText);
int cutOffIndex = layout.getLineEnd(mMaxLines - 1);
int textLength = fullText.length();
int cutOffLength = textLength - cutOffIndex;
if (cutOffLength < ELLIPSIS.length()) cutOffLength = ELLIPSIS.length();
String workingText = TextUtils.substring(fullText, 0, textLength - cutOffLength).trim();
String strippedText = stripEndPunctuation(workingText);
while (!isInLayout(strippedText + ELLIPSIS)) {
int lastSpace = workingText.lastIndexOf(' ');
if (lastSpace == -1) break;
workingText = workingText.substring(0, lastSpace).trim();
strippedText = stripEndPunctuation(workingText);
}
workingText = strippedText + ELLIPSIS;
SpannableStringBuilder dest = new SpannableStringBuilder(workingText);
if (fullText instanceof Spanned) {
TextUtils.copySpansFrom((Spanned) fullText, 0, workingText.length(), null, dest, 0);
}
return dest;
}
public String stripEndPunctuation(CharSequence workingText) {
return mEndPunctPattern.matcher(workingText).replaceFirst("");
}
}
private class EllipsizeStartStrategy extends EllipsizeStrategy {
@Override
protected CharSequence createEllipsizedText(CharSequence fullText) {
Layout layout = createWorkingLayout(fullText);
int cutOffIndex = layout.getLineEnd(mMaxLines - 1);
int textLength = fullText.length();
int cutOffLength = textLength - cutOffIndex;
if (cutOffLength < ELLIPSIS.length()) cutOffLength = ELLIPSIS.length();
String workingText = TextUtils.substring(fullText, cutOffLength, textLength).trim();
while (!isInLayout(ELLIPSIS + workingText)) {
int firstSpace = workingText.indexOf(' ');
if (firstSpace == -1) break;
workingText = workingText.substring(firstSpace, workingText.length()).trim();
}
workingText = ELLIPSIS + workingText;
SpannableStringBuilder dest = new SpannableStringBuilder(workingText);
if (fullText instanceof Spanned) {
TextUtils.copySpansFrom((Spanned) fullText, textLength - workingText.length(),
textLength, null, dest, 0);
}
return dest;
}
}
private class EllipsizeMiddleStrategy extends EllipsizeStrategy {
@Override
protected CharSequence createEllipsizedText(CharSequence fullText) {
Layout layout = createWorkingLayout(fullText);
int cutOffIndex = layout.getLineEnd(mMaxLines - 1);
int textLength = fullText.length();
int cutOffLength = textLength - cutOffIndex;
if (cutOffLength < ELLIPSIS.length()) cutOffLength = ELLIPSIS.length();
cutOffLength += cutOffIndex % 2; // Make it even.
String firstPart = TextUtils.substring(
fullText, 0, textLength / 2 - cutOffLength / 2).trim();
String secondPart = TextUtils.substring(
fullText, textLength / 2 + cutOffLength / 2, textLength).trim();
while (!isInLayout(firstPart + ELLIPSIS + secondPart)) {
int lastSpaceFirstPart = firstPart.lastIndexOf(' ');
int firstSpaceSecondPart = secondPart.indexOf(' ');
if (lastSpaceFirstPart == -1 || firstSpaceSecondPart == -1) break;
firstPart = firstPart.substring(0, lastSpaceFirstPart).trim();
secondPart = secondPart.substring(firstSpaceSecondPart, secondPart.length()).trim();
}
SpannableStringBuilder firstDest = new SpannableStringBuilder(firstPart);
SpannableStringBuilder secondDest = new SpannableStringBuilder(secondPart);
if (fullText instanceof Spanned) {
TextUtils.copySpansFrom((Spanned) fullText, 0, firstPart.length(),
null, firstDest, 0);
TextUtils.copySpansFrom((Spanned) fullText, textLength - secondPart.length(),
textLength, null, secondDest, 0);
}
return TextUtils.concat(firstDest, ELLIPSIS, secondDest);
}
}
}
Complete source: EllipsizingTextView.java
Got this problem to, and finaly, I build myself a short solution. You just have to ellipsize manually the line you want, your maxLine attribute will cut your text.
This example cut your text for 3 lines max
final TextView title = (TextView)findViewById(R.id.text);
title.setText("A really long text");
ViewTreeObserver vto = title.getViewTreeObserver();
vto.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
ViewTreeObserver obs = title.getViewTreeObserver();
obs.removeGlobalOnLayoutListener(this);
if(title.getLineCount() > 3){
Log.d("","Line["+title.getLineCount()+"]"+title.getText());
int lineEndIndex = title.getLayout().getLineEnd(2);
String text = title.getText().subSequence(0, lineEndIndex-3)+"...";
title.setText(text);
Log.d("","NewText:"+text);
}
}
});
In my case, there is no need to code this in Java. Everything works as expected. No need for something like android:singleLine="false"
.
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="4"
android:text="@string/very_long_text" />
But there seems to be a bug in the layout preview of Android Studio (v3.0):
Given Android 7.1.1 on my device this is working:
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With