android ellipsize multiline textview
I need to ellipsize a multi-line textview. My component is large enough to display at least 4 lines with the ellipse, but only 2 lines are displayed. I tried to change the minimum and maximum number of rows of the component but it changes nothing.
Asked by: Ryan242 | Posted: 24-01-2022
Answer 1
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
}
}
Answered by: Brianna905 | Posted: 25-02-2022
Answer 2
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"
Answered by: Stella502 | Posted: 25-02-2022
Answer 3
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!" />
Answered by: Patrick831 | Posted: 25-02-2022
Answer 4
I've run into this problem, too. There's a rather old bug about it that remains unanswered: Bug 2254
Answered by: Clark553 | Posted: 25-02-2022Answer 5
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
Answered by: Elise367 | Posted: 25-02-2022Answer 6
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);
}
}
});
Answered by: Alberta398 | Posted: 25-02-2022
Answer 7
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:
Answer 8
Based on the solutions by Micah Hainline and alebs comment, I came of with the following approach that works with Spanned texts, so that e.g. myTextView.setText(Html.fromHtml("<b>Testheader</b> - Testcontent"));
works!
Note that this only works with Spanned
right now. It could maybe be modified to work with String
and Spanned
either way.
public class EllipsizingTextView extends TextView {
private static final Spanned ELLIPSIS = new SpannedString("…");
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 Spanned fullText;
private int maxLines;
private float lineSpacingMultiplier = 1.0f;
private float lineAdditionalVerticalPadding = 0.0f;
public EllipsizingTextView(Context context) {
this(context, null);
}
public EllipsizingTextView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public EllipsizingTextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
super.setEllipsize(null);
TypedArray a = context.obtainStyledAttributes(attrs, new int[] { android.R.attr.maxLines });
setMaxLines(a.getInt(0, Integer.MAX_VALUE));
}
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;
}
public boolean ellipsizingLastFullyVisibleLine() {
return maxLines == Integer.MAX_VALUE;
}
@Override
public void setLineSpacing(float add, float mult) {
this.lineAdditionalVerticalPadding = add;
this.lineSpacingMultiplier = mult;
super.setLineSpacing(add, mult);
}
@Override
public void setText(CharSequence text, BufferType type) {
if (!programmaticChange && text instanceof Spanned) {
fullText = (Spanned) 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;
}
}
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(Canvas canvas) {
if (isStale) {
resetText();
}
super.onDraw(canvas);
}
private void resetText() {
Spanned workingText = fullText;
boolean ellipsized = false;
Layout layout = createWorkingLayout(workingText);
int linesCount = getLinesCount();
if (layout.getLineCount() > linesCount) {
// We have more lines of text than we are allowed to display.
workingText = (Spanned) fullText.subSequence(0, layout.getLineEnd(linesCount - 1));
while (createWorkingLayout((Spanned) TextUtils.concat(workingText, ELLIPSIS)).getLineCount() > linesCount) {
int lastSpace = workingText.toString().lastIndexOf(' ');
if (lastSpace == -1) {
break;
}
workingText = (Spanned) workingText.subSequence(0, lastSpace);
}
workingText = (Spanned) TextUtils.concat(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);
}
}
}
/**
* Get how many lines of text we are allowed to display.
*/
private int getLinesCount() {
if (ellipsizingLastFullyVisibleLine()) {
int fullyVisibleLinesCount = getFullyVisibleLinesCount();
if (fullyVisibleLinesCount == -1) {
return 1;
} else {
return fullyVisibleLinesCount;
}
} else {
return maxLines;
}
}
/**
* Get how many lines of text we can display so their full height is visible.
*/
private int getFullyVisibleLinesCount() {
Layout layout = createWorkingLayout(new SpannedString(""));
int height = getHeight() - getPaddingTop() - getPaddingBottom();
int lineHeight = layout.getLineBottom(0);
return height / lineHeight;
}
private Layout createWorkingLayout(Spanned workingText) {
return new StaticLayout(workingText, getPaint(),
getWidth() - getPaddingLeft() - getPaddingRight(),
Alignment.ALIGN_NORMAL, lineSpacingMultiplier,
lineAdditionalVerticalPadding, false /* includepad */);
}
@Override
public void setEllipsize(TruncateAt where) {
// Ellipsize settings are not respected
}
}
Answered by: Adelaide805 | Posted: 25-02-2022
Answer 9
For those who are interested, here's a C# Xamarin.Android port of Micah's lovely solution:
public delegate void EllipsizeEvent(bool ellipsized);
public class EllipsizingTextView : TextView
{
private const string Ellipsis = "...";
public event EllipsizeEvent EllipsizeStateChanged;
private bool isEllipsized;
private bool isStale;
private bool programmaticChange;
private string fullText;
private int maxLines = -1;
private float lineSpacingMultiplier = 1.0f;
private float lineAdditionalVerticalPadding;
public EllipsizingTextView(Context context) : base(context)
{
}
public EllipsizingTextView(Context context, IAttributeSet attrs) : base(context, attrs)
{
}
public EllipsizingTextView(Context context, IAttributeSet attrs, int defStyle) : base(context, attrs, defStyle)
{
}
public EllipsizingTextView(IntPtr javaReference, JniHandleOwnership transfer) : base(javaReference, transfer)
{
}
public bool IsEllipsized
{
get { return isEllipsized; }
}
public override void SetMaxLines(int maxLines) {
base.SetMaxLines(maxLines);
this.maxLines = maxLines;
isStale = true;
}
public int GetMaxLines()
{
return maxLines;
}
public override void SetLineSpacing(float add, float mult)
{
lineAdditionalVerticalPadding = add;
lineSpacingMultiplier = mult;
base.SetLineSpacing(add, mult);
}
protected override void OnTextChanged(ICharSequence text, int start, int before, int after)
{
base.OnTextChanged(text, start, before, after);
if (!programmaticChange)
{
fullText = text.ToString();
isStale = true;
}
}
protected override void OnDraw(Canvas canvas)
{
if (isStale)
{
base.Ellipsize = null;
ResetText();
}
base.OnDraw(canvas);
}
private void ResetText()
{
int maxLines = GetMaxLines();
string workingText = fullText;
bool ellipsized = false;
if (maxLines != -1)
{
Layout layout = CreateWorkingLayout(workingText);
if (layout.LineCount > maxLines)
{
workingText = fullText.Substring(0, layout.GetLineEnd(maxLines - 1)).Trim();
while (CreateWorkingLayout(workingText + Ellipsis).LineCount > maxLines)
{
int lastSpace = workingText.LastIndexOf(' ');
if (lastSpace == -1)
{
break;
}
workingText = workingText.Substring(0, lastSpace);
}
workingText = workingText + Ellipsis;
ellipsized = true;
}
}
if (workingText != Text)
{
programmaticChange = true;
try
{
Text = workingText;
}
finally
{
programmaticChange = false;
}
}
isStale = false;
if (ellipsized != isEllipsized)
{
isEllipsized = ellipsized;
if (EllipsizeStateChanged != null)
EllipsizeStateChanged(ellipsized);
}
}
private Layout CreateWorkingLayout(string workingText)
{
return new StaticLayout(workingText, Paint, Width - PaddingLeft - PaddingRight, Layout.Alignment.AlignNormal, lineSpacingMultiplier, lineAdditionalVerticalPadding, false);
}
public override TextUtils.TruncateAt Ellipsize
{
get
{
return base.Ellipsize;
}
set
{
}
}
}
Answered by: Chelsea867 | Posted: 25-02-2022
Answer 10
To add ...
in the end of the second line, saving 1 line if text is short:
android:maxLines="2"
android:ellipsize="end"
Answered by: Carina106 | Posted: 25-02-2022
Answer 11
Just add code in your activity
Textview.setEllipsize(TextUtils.TruncateAt.END)
this will add ellipsis at the end of the textview
Answered by: Patrick678 | Posted: 25-02-2022Answer 12
Extend TextView and override these methods:
CharSequence origText = "";
int maxLines = 2;
@Override
public void setText(CharSequence text, BufferType type) {
super.setText(text, type);
origText = text;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
CharSequence text = origText;
onPreDraw();
while(getLineCount() > maxLines) {
text = text.subSequence(0, text.length()-1);
super.setText(text + "...");
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
onPreDraw();
}
}
Answered by: Arthur317 | Posted: 25-02-2022
Answer 13
This is my solution. you can download demo on my github. https://github.com/krossford/KrossLib/tree/master/android-project
This screenshot was a demo that maxLines = 4
, I think it works well.
package com.krosshuang.krosslib.lib.view;
import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.widget.TextView;
import java.util.ArrayList;
/*
如何使用?
How to use it?
> 1.在xml或者java代ç ä¸å¸¸è§„使用
> 1.use it like other views on xml and java code.
> 2.[å¿…é¡»] setMaxLines 方法替代在xmlä¸çš„ "android:maxLines" 属性
> 2.[must] call the setMaxLines method to instead of the xml property android:maxLines.
> 3.[å¯é€‰] 注æ„调用 setMultilineEllipsizeMode() 方法,具体请查看注释
> 3.[option] you can invoke setMultilineEllipsizeMode method, but I have not implement it.
*/
/**
* android自己的TextView对多行ellipsize处ç†çš„ä¸å¥½
* Created by krosshuang on 2015/12/17.
*/
public class EllipsizeEndTextView extends TextView {
private static final String LOG_TAG = "EllipsizeTextView";
/** æ¯ä¸€è¡Œéƒ½æœ‰çœç•¥å· */
//TODO 该特性待完æˆ
public static final int MODE_EACH_LINE = 1;
/** 最åŽä¸€è¡Œæ‰æœ‰çœç•¥å· */
public static final int MODE_LAST_LINE = 2;
private static final String ELLIPSIZE = "...";
private ArrayList<String> mTextLines = new ArrayList<String>();
private CharSequence mSrcText = null;
private int mMultilineEllipsizeMode = MODE_LAST_LINE;
private int mMaxLines = 1;
private boolean mNeedIgnoreTextChangeAndSelfInvoke = false;
public EllipsizeEndTextView(Context context) {
super(context);
}
public EllipsizeEndTextView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public EllipsizeEndTextView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
if (!mNeedIgnoreTextChangeAndSelfInvoke) {
super.onTextChanged(text, start, lengthBefore, lengthAfter);
mSrcText = text;
}
}
@Override
public void setMaxLines(int maxlines) {
super.setMaxLines(maxlines);
mMaxLines = maxlines;
}
public int getSupportedMaxLines() {
return mMaxLines;
}
@Override
protected void onDraw(Canvas canvas) {
setVisibleText();
super.onDraw(canvas);
mNeedIgnoreTextChangeAndSelfInvoke = false;
}
private void setVisibleText() {
if (mSrcText == null) {
return;
}
//获得å¯ä½¿ç”¨çš„width get available width
final int aw = getWidth() - getPaddingLeft() - getPaddingRight();
String srcText = mSrcText.toString();
//将原始的å—符串先按原始数æ®ä¸å˜åœ¨çš„æ¢è¡Œç¬¦å¼„æˆå¤šè¡Œå—符串
String[] lines = srcText.split("\n");
//Log.i(LOG_TAG, "åŽŸå§‹æ•°æ®æœ‰: " + lines.length + " 行 " + Arrays.toString(lines));
int maxLines = getSupportedMaxLines();
//将原始文本分æˆå‡ 行åŽåŠ å…¥list
mTextLines.clear();
for (int i = 0; i < lines.length; i++) {
mTextLines.add(lines[i]);
}
switch (mMultilineEllipsizeMode) {
case MODE_EACH_LINE:
break;
default:
case MODE_LAST_LINE:
//开始é历
String eachLine = null;
for (int i = 0; i < mTextLines.size() && i < maxLines - 1; i++) {
eachLine = mTextLines.get(i);
if (getPaint().measureText(eachLine, 0, eachLine.length()) > aw) {
//当å‰è¡Œè¶…过å¯ç”¨å®½åº¦
boolean isOut = true;
int end = eachLine.length() - 1;
while (isOut) {
if (getPaint().measureText(eachLine.substring(0, end), 0, end) > aw) {
end--;
} else {
isOut = false;
}
}
mTextLines.set(i, eachLine.substring(0, end)); //当å‰è¡Œè®¾ç½®ä¸ºè£å‰ªåŽçš„
mTextLines.add(i + 1, eachLine.substring(end, eachLine.length())); //å°†è£å‰ªå‰©ä½™çš„éƒ¨åˆ†ï¼ŒåŠ å…¥ä¸‹ä¸€è¡Œï¼Œåˆšå¥½æŽ¥ä¸‹æ¥å‘生的é历就å¯ä»¥å¤„ç†å®ƒï¼Œç›¸å½“于一个递归
}
}
//é历处ç†ç»“æŸï¼Œæ‰€æœ‰çš„行都是在å¯ç”¨å®½åº¦ä»¥å†…çš„
break;
}
//æ ¹æ® maxLines å’Œ 结果的行数,决定最å°éœ€è¦å¤šå°‘行
int resultSize = Math.min(maxLines, mTextLines.size());
//对最åŽä¸€è¡Œåšå¤„ç†
String lastLine = mTextLines.get(resultSize - 1);
//最åŽä¸€è¡Œæœ‰ä¸¤ç§æƒ…况需è¦åŠ ...
//1.最åŽä¸€è¡Œæ•°æ®æœ¬èº«å¾ˆé•¿ï¼Œè¶…过了å¯ç”¨å®½åº¦ï¼Œé‚£ä¹ˆè£å‰ªåŽå°¾éƒ¨åŠ ä¸Š...
//2.最åŽä¸€è¡Œä¸æ˜¯å¾ˆé•¿ï¼Œå¹¶æ²¡æœ‰è¶…过å¯ç”¨å®½åº¦ï¼Œä½†æ˜¯å®ƒåº•ä¸‹è¿˜æœ‰è¡Œæ²¡æœ‰æ˜¾ç¤ºï¼Œå› æ¤åŠ ä¸Š...
if (getPaint().measureText(lastLine, 0, lastLine.length()) > aw || resultSize < mTextLines.size()) {
boolean isOut = true;
int end = lastLine.length();
while (isOut) {
if (getPaint().measureText(lastLine.substring(0, end) + ELLIPSIZE, 0, end + 3) > aw) {
end--;
} else {
isOut = false;
}
}
mTextLines.set(resultSize - 1, lastLine.substring(0, end) + ELLIPSIZE);
}
//开始构建结果
StringBuilder sb = new StringBuilder();
for (int i = 0; i < resultSize ; i++) {
sb.append(mTextLines.get(i));
if (i != resultSize - 1) {
sb.append('\n');
}
}
//构建完æˆï¼Œset
if (sb.toString().equals(getText())) {
return;
} else {
mNeedIgnoreTextChangeAndSelfInvoke = true;
setText(sb.toString());
}
}
/**
* 设置ellipsize modeï¼Œæš‚æ—¶ä¸æ”¯æŒ
* @deprecated
* */
public void setMultilineEllipsizeMode(int mode) {
mMultilineEllipsizeMode = mode;
}
}
Answered by: Kellan553 | Posted: 25-02-2022
Answer 14
This is late, but I found an Apache licensed class from Android, that's used in the stock mail app: https://android.googlesource.com/platform/packages/apps/UnifiedEmail/+/184ec73/src/com/android/mail/ui/EllipsizedMultilineTextView.java
/*
* Copyright (C) 2013 Google Inc.
* Licensed to The Android Open Source Project.
*
* 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.
*/
package com.android.mail.ui;
import android.content.Context;
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.util.AttributeSet;
import android.widget.TextView;
/**
* A special MultiLine TextView that will apply ellipsize logic to only the last
* line of text, such that the last line may be shorter than any previous lines.
*/
public class EllipsizedMultilineTextView extends TextView {
public static final int ALL_AVAILABLE = -1;
private int mMaxLines;
public EllipsizedMultilineTextView(Context context) {
this(context, null);
}
public EllipsizedMultilineTextView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public void setMaxLines(int maxlines) {
super.setMaxLines(maxlines);
mMaxLines = maxlines;
}
/**
* Ellipsize just the last line of text in this view and set the text to the
* new ellipsized value.
* @param text Text to set and ellipsize
* @param avail available width in pixels for the last line
* @param paint Paint that has the proper properties set to measure the text
* for this view
* @return the {@link CharSequence} that was set on the {@link TextView}
*/
public CharSequence setText(final CharSequence text, int avail) {
if (text == null || text.length() == 0) {
return text;
}
setEllipsize(null);
setText(text);
if (avail == ALL_AVAILABLE) {
return text;
}
Layout layout = getLayout();
if (layout == null) {
final int w = getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight();
layout = new StaticLayout(text, 0, text.length(), getPaint(), w, Alignment.ALIGN_NORMAL,
1.0f, 0f, false);
}
// find the last line of text and chop it according to available space
final int lastLineStart = layout.getLineStart(mMaxLines - 1);
final CharSequence remainder = TextUtils.ellipsize(text.subSequence(lastLineStart,
text.length()), getPaint(), avail, TextUtils.TruncateAt.END);
// assemble just the text portion, without spans
final SpannableStringBuilder builder = new SpannableStringBuilder();
builder.append(text.toString(), 0, lastLineStart);
if (!TextUtils.isEmpty(remainder)) {
builder.append(remainder.toString());
}
// Now copy the original spans into the assembled string, modified for any ellipsizing.
//
// Merely assembling the Spanned pieces together would result in duplicate CharacterStyle
// spans in the assembled version if a CharacterStyle spanned across the lastLineStart
// offset.
if (text instanceof Spanned) {
final Spanned s = (Spanned) text;
final Object[] spans = s.getSpans(0, s.length(), Object.class);
final int destLen = builder.length();
for (int i = 0; i < spans.length; i++) {
final Object span = spans[i];
final int start = s.getSpanStart(span);
final int end = s.getSpanEnd(span);
final int flags = s.getSpanFlags(span);
if (start <= destLen) {
builder.setSpan(span, start, Math.min(end, destLen), flags);
}
}
}
setText(builder);
return builder;
}
}
Answered by: Roland203 | Posted: 25-02-2022
Answer 15
I have had the same Problem. I fixed it by just deleting android:ellipsize="marquee"
Answered by: Vanessa793 | Posted: 25-02-2022Answer 16
Code worked very well! You can overload onSizeChanged method, if not only Text has to be Changed.
@Override
protected void onSizeChanged (int w, int h, int oldw, int oldh) {
isStale = true;
super.onSizeChanged(w, h, oldw, oldh);
}
Answered by: Elise332 | Posted: 25-02-2022
Answer 17
This one handled my html as well,
/*
* Copyright (C) 2013 Google Inc.
* Licensed to The Android Open Source Project.
*
* 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.
*/
package com.android.mail.ui;
import android.content.Context;
import android.content.res.TypedArray;
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.util.AttributeSet;
import android.widget.TextView;
/**
* A special MultiLine TextView that will apply ellipsize logic to only the last
* line of text, such that the last line may be shorter than any previous lines.
*/
public class EllipsizedMultilineTextView extends TextView {
public static final int ALL_AVAILABLE = -1;
private int mMaxLines;
public EllipsizedMultilineTextView(Context context) {
super(context);
}
public EllipsizedMultilineTextView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
public EllipsizedMultilineTextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context, attrs);
}
private final void init(Context context, AttributeSet attrs) {
final TypedArray a = context.obtainStyledAttributes(attrs,
new int[] { android.R.attr.maxLines });
setMaxLines(a.getInt(0, 2));
}
@Override
public void setMaxLines(int maxlines) {
super.setMaxLines(maxlines);
mMaxLines = maxlines;
}
/**
* Ellipsize just the last line of text in this view and set the text to the
* new ellipsized value.
* @param text Text to set and ellipsize
* @param avail available width in pixels for the last line
* @param paint Paint that has the proper properties set to measure the text
* for this view
* @return the {@link CharSequence} that was set on the {@link TextView}
*/
public CharSequence setText(final CharSequence text, int avail) {
if (text == null || text.length() == 0) {
return text;
}
setEllipsize(null);
setText(text);
if (avail == ALL_AVAILABLE) {
return text;
}
Layout layout = getLayout();
if (layout == null) {
final int w = getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight();
layout = new StaticLayout(text, 0, text.length(), getPaint(), w, Alignment.ALIGN_NORMAL,
1.0f, 0f, false);
}
// find the last line of text and chop it according to available space
final int lastLineStart = layout.getLineStart(mMaxLines - 1);
final CharSequence remainder = TextUtils.ellipsize(text.subSequence(lastLineStart,
text.length()), getPaint(), avail, TextUtils.TruncateAt.END);
// assemble just the text portion, without spans
final SpannableStringBuilder builder = new SpannableStringBuilder();
builder.append(text.toString(), 0, lastLineStart);
if (!TextUtils.isEmpty(remainder)) {
builder.append(remainder.toString());
}
// Now copy the original spans into the assembled string, modified for any ellipsizing.
//
// Merely assembling the Spanned pieces together would result in duplicate CharacterStyle
// spans in the assembled version if a CharacterStyle spanned across the lastLineStart
// offset.
if (text instanceof Spanned) {
final Spanned s = (Spanned) text;
final Object[] spans = s.getSpans(0, s.length(), Object.class);
final int destLen = builder.length();
for (int i = 0; i < spans.length; i++) {
final Object span = spans[i];
final int start = s.getSpanStart(span);
final int end = s.getSpanEnd(span);
final int flags = s.getSpanFlags(span);
if (start <= destLen) {
builder.setSpan(span, start, Math.min(end, destLen), flags);
}
}
}
setText(builder);
return builder;
}
}
Orignal Source LINK
Answered by: Miller212 | Posted: 25-02-2022Answer 18
The top answer here from Micah Hainline works great, but even better is the library that was built from it by user aleb as he posted in the comments under Micahs answer:
I created an Android library with this component and changed it to be able to show as many lines of text as possible and ellipsize the last one; see github.com/triposo/barone
There are some more features to it, if you only need the TextView, it is here.
Maybe this will help others find it faster than I did :-)
Answered by: Dainton261 | Posted: 25-02-2022Answer 19
There's no need to do extra codes in java just do this:
<TextView
android:id="@+id/productDescription"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxLines="4"
android:ellipsize="end"
android:singleLine="false"
android:text="Vehicle Compatibility (Brand/Model) - Hero Ez, Hero Maestro, Hero Pleasure, Honda Activa, Honda Dio, Piaggio Vespa, Suzuki Access, Suzuki Swish, Yamaha Alpha, Yamaha Fascino, Yamaha Ray\nFeatures: Higher NSD with Connected Blocks, Continuous centre Groove, Deeper Shoulder Grooves and Sipes on the tread Blocks\nFunctions: More rubber to wear ; Construction Type: Bias ; Grip: Excellent Dry & Wet Grip ; Functions:Functions: More rubber to wear Better mass transfer, More 'biting' edges to Grip on or Off road" />
Screenshot:
Answered by: Nicole434 | Posted: 25-02-2022Answer 20
I've found that using ConstraintLayout with start and end bounds setup plus layout_width="0dp" sees the ellipses for multiline TextView. A solution for AppWidget remains at large.
Answered by: Sam525 | Posted: 25-02-2022Answer 21
There are a few attributes you should check: android:lines, android:minLines, android:maxLines. To display a maximum of 4 lines and ellipsize it, you just need android:maxLines and android:ellipsize:
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:maxLines="4"
android:ellipsize="marquee"
android:text="Hai!"
/>
Answered by: Sarah840 | Posted: 25-02-2022
Answer 22
You need to include this in your textview:
android:singleLine="false"
by default its true. You need to explicitly set it false.
Answered by: Dexter922 | Posted: 25-02-2022Similar questions
Android: TextView ellipsize multiline
This question already has answers here:
Android: TextView ellipsize multiline
This question already has answers here:
android - Ellipsize not working properly for a multiline TextView with an arbitrary maximum height
I have a TextView with an unknown maximum height, which is dependent on the device's DPI/screen resolution. So, for instance, on and MDPI device this maximum height makes it possible to show only 2 lines at a time, a value that can be increased up to an undefined number.
My issue is related with the ellipsize functionality. Let's suppose that a certain device allows for 4 lines to be displayed. If I ma...
android - Restore TextView single line ellipsize mode after using multiline scrolling mode
I have TextView with large text. Initially, the TextView displaying as single line, without scrolling and with "..." at the end of the string (android:ellipsize="end"):
<TextView
android:id="@+id/myTextView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:maxLines="1"
android:ellipsize="end"
android:scrollbars="vertical"
android:te...
android - How to set maxLines and ellipsize of a TextView at the same time
I want to limit my text view to have maximum of 5 lines, so I did:
<TextView
android:id="@+id/text"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:maxLines="5" />
But when I try to configure it to add '...' when the text is truncated, I add android:ellipsize="end". I do see the ... but then my TextView only has a max line of 2, inst...
android - Can I ellipsize a dialog title?
I have an Activity running as a Theme.Dialog. Can I set its title to an "ellipsize" style? Currently, the text is being populated in onCreate() with setText(). There's no this.setEllipsize() like on TextView.
Or, is my only option to write a Custom Dialog and set the title style there? I'd like to know if that's possible be...
listview - Android Ellipsize marquee issues with currently selected list item
My Question - I want the text of a TextView I have in a custom ListView to marquee scroll ONLY when the item is selected in the ListView. When the list item loses focus, I want the marquee to stop scrolling. I know that I need to set the TextView to enabled (setEnabled(true)) for scrolling to work however when I do this as items are added, they ALL scroll, which is not the behavior I want ...
android - TextView and Ellipsize when squeezed by other TextView
I have a relativelayout, and in it two textviews. Textview 1 is aligned top and left with parent. Textview 2 is aligned to the right of textview 1, and to the right with parent. This creates a problem if the text in 1 is too long, meaning that 1 will push 2 out of the screen worst case.
How do I get these two to "sacrifice" from text nr 1 instead of 2? min width does not seem to work. My final goal is to ellipsize ...
android - Spinner / Ellipsize / Width
I've a spinner pointing on a CursorAdapter.
I set this adapter with a specific view.
I would like to fix the spinner's width, I tried a lot of stuff unsucessfully :
layout_width="100dp" // to force spinner with a specific size KO
reduce the layout width to 100dp // KO
use an ellipsize on a textview contained by the adapter // KO
I would like to block the spinner's width. Do yo...
android - Text does not ellipsize
I have a TextView set to ellipsize but when I put the text in it doesn't do so. Any ideas?
<TextView
android:id="@+id/LegalsView"
android:ellipsize="end"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceMedium" />
Android: TextView ellipsize multiline
This question already has answers here:
android - how to know if the string have ellipsize
i have a string in the textView as:
<TextView
android:id="@+id/comment_concent"
android:maxLines="2"
android:textColor="#FFCD3333"
android:ellipsize="end"
android:layout_marginRight="5dip"
android:layout_width="210dip"
android:layout_height="50dip"
android:textSize="15sp"
android:text...
Android ListView ellipsize wide column content
I have a ListView that shows 2 columns per row. I don't have any specific column layout XML so Android is determining it for me. If the text in the 1st column is too wide then bad things happen to the layout of the 2nd column. Ideally I want Android to ellipsize the text in the 1st column at a sensible width and/or restrict the 1st column's width to 80% of the total displayable width. Can I do this in code?
Am ...
layout - Ellipsize attribute not working on Android 4.0
I'm currently testing an app on Android 4.0 Ice Cream Sandwich before releasing an update to the market. During the tests I realized that the ellipsize attribute stopped working.
I used it on a list view to truncate item titles which were to long. On Android 2.3.7 everything renders as expected. Titles that are too long get truncated and three dots are append to the end of the string. On Android 4.0 the title gets...
Still can't find your answer? Check out these communities...
Android Google Support | Android Community | Android Community (Facebook) | Dev.io Android