Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

TextView using Spannable - ellipsize doesn't work

Tags:

android

The issue I'm trying to fix is the following: I'm having a TextView and I'm using a Spannable to set some characters bold. The text needs to have a maxim of 2 lines ( android:maxLines="2") and I want the text to be ellipsized, but for some reason I cannot make the text ellipsized.

Here is the simple code:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:orientation="vertical"
              android:layout_width="fill_parent"
              android:layout_height="fill_parent">

    <TextView android:id="@+id/name"
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:gravity="center"
              android:maxLines="2"
              android:ellipsize="end"
              android:bufferType="spannable"
              android:text="@string/app_name"
              android:textSize="15dp"/>

</LinearLayout>

and the activity:

public class MyActivity extends Activity {

    private TextView name;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        name= (TextView) findViewById(R.id.name);


        name.setText("Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy ");
        Spannable spannable = (Spannable)name.getText();
        StyleSpan boldSpan = new StyleSpan( Typeface.BOLD );
        spannable.setSpan( boldSpan, 10, 15, Spannable.SPAN_INCLUSIVE_INCLUSIVE );

    }
}

The text is truncated, no "..." are displayed. enter image description here

like image 809
Paul Avatar asked Feb 04 '13 16:02

Paul


People also ask

What is Ellipsize in TextView?

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.

How do I assign text to TextView?

Set The Text of The TextView You can set the text to be displayed in the TextView either when declaring it in your layout file, or by using its setText() method. The text is set via the android:text attribute. You can either set the text as attribute value directly, or reference a text defined in the strings.

How do I add a newline to a TextView in android?

for the new line in TextView just add \n in middle of your text it works..

What is Ellipsize marquee?

If you want a horizontal scrollable text in your app, use android:ellipsize="marquee" where a single line large text will be scrolling.


8 Answers

I realise this is a very old post, but seeing as it's still unanswered and I also ran into this issue today I thought I would post a solution to this. Hopefully it helps someone in the future.

ViewTreeObserver viewTreeObserver = textView.getViewTreeObserver();
viewTreeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener()
{
    @Override
    public void onGlobalLayout()
    {
        ViewTreeObserver viewTreeObserver = textView.getViewTreeObserver();
        viewTreeObserver.removeOnGlobalLayoutListener(this);

        if (textView.getLineCount() > 5)
        {
            int endOfLastLine = textView.getLayout().getLineEnd(4);
            String newVal = textView.getText().subSequence(0, endOfLastLine - 3) + "...";
            textView.setText(newVal);
        }
    }
});
like image 71
Dallas187 Avatar answered Sep 29 '22 23:09

Dallas187


Having same problem and seems the following works for me:

Spannable wordtoSpan = new SpannableString(lorem); 
wordtoSpan.setSpan(new ForegroundColorSpan(0xffff0000), 0, 10, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
wordtoSpan.setSpan(new ForegroundColorSpan(0xff00ffff), 20, 35, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
textView.setText(wordtoSpan);

in xml the textView has android:multiLine set, and android:ellipsize="end", and android:singleLine="false;

like image 45
lannyf Avatar answered Sep 29 '22 23:09

lannyf


You're right in that ellipsize, declared either in xml or in code won't work on spannable text.

However, with a little bit of investigation you can actually do the ellipsizing yourself:

private TextView name;

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);

    name= (TextView) findViewById(R.id.name);
    String lorem = "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy "
    name.setText(lorem);

    Spannable spannable = (Spannable)name.getText();
    StyleSpan boldSpan = new StyleSpan(Typeface.BOLD);
    spannable.setSpan( boldSpan, 10, 15, Spannable.SPAN_INCLUSIVE_INCLUSIVE);
    int maxLines = 2;
    // in my experience, this needs to be called in code, your mileage may vary.
    name.setMaxLines(maxLines);

    // check line count.. this will actually be > than the # of visible lines
    // if it is long enough to be truncated
    if (name.getLineCount() > maxLines){
        // this returns _1 past_ the index of the last character shown 
        // on the indicated line. the lines are zero indexed, so the last 
        // valid line is maxLines -1; 
        int lastCharShown = name.getLayout().getLineVisibleEnd(maxLines - 1); 
        // chop off some characters. this value is arbitrary, i chose 3 just 
        // to be conservative.
        int numCharsToChop = 3;
        String truncatedText = lorem.substring(0, lastCharShown - numCharsToChop);
        // ellipsize! note ellipsis character.
        name.setText(truncatedText+"…");
        // reapply the span, since the text has been changed.
        spannable.setSpan(boldSpan, 10, 15, Spannable.SPAN_INCLUSIVE_INCLUSIVE);
    }

}
like image 27
lim Avatar answered Sep 29 '22 23:09

lim


This may a little trick by using reflection to solve this problem. After reading the source code of AOSP, in TextView.java, DynamicLayout only contains a static field member named sStaticLayout and it's constructed by new StaticLayout(null) without any params including maxLines.

Therefore, doEllipsis will alway be false as mMaximumVisibleLineCount is set Integer.MAX_VALUE by default.

boolean firstLine = (j == 0);
boolean currentLineIsTheLastVisibleOne = (j + 1 == mMaximumVisibleLineCount);
boolean lastLine = currentLineIsTheLastVisibleOne || (end == bufEnd);

    ......

if (ellipsize != null) {
    // If there is only one line, then do any type of ellipsis except when it is MARQUEE
    // if there are multiple lines, just allow END ellipsis on the last line
    boolean forceEllipsis = moreChars && (mLineCount + 1 == mMaximumVisibleLineCount);

    boolean doEllipsis =
                (((mMaximumVisibleLineCount == 1 && moreChars) || (firstLine && !moreChars)) &&
                        ellipsize != TextUtils.TruncateAt.MARQUEE) ||
                (!firstLine && (currentLineIsTheLastVisibleOne || !moreChars) &&
                        ellipsize == TextUtils.TruncateAt.END);
    if (doEllipsis) {
        calculateEllipsis(start, end, widths, widthStart,
                ellipsisWidth, ellipsize, j,
                textWidth, paint, forceEllipsis);
    }
}

So I extends the TextView and make a View named EllipsizeTextView

public class EllipsizeTextView extends TextView {
public EllipsizeTextView(Context context) {
    super(context);
}

public EllipsizeTextView(Context context, AttributeSet attrs) {
    super(context, attrs);
}

public EllipsizeTextView(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
}

@Override
protected void onDetachedFromWindow() {
    super.onDetachedFromWindow();
}

public EllipsizeTextView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
    super(context, attrs, defStyleAttr, defStyleRes);
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    StaticLayout layout = null;
    Field field = null;
    try {
        Field staticField = DynamicLayout.class.getDeclaredField("sStaticLayout");
        staticField.setAccessible(true);
        layout = (StaticLayout) staticField.get(DynamicLayout.class);
    } catch (NoSuchFieldException e) {
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    }

    if (layout != null) {
        try {
            field = StaticLayout.class.getDeclaredField("mMaximumVisibleLineCount");
            field.setAccessible(true);
            field.setInt(layout, getMaxLines());
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    if (layout != null && field != null) {
        try {
            field.setInt(layout, Integer.MAX_VALUE);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }
}

}

problem solved!

like image 20
Sheldon Xia Avatar answered Sep 28 '22 23:09

Sheldon Xia


Another solution is to overwrite onDraw of TextView. The following suggested solution, doesn't make use any reflection technique. Hence, shouldn't break in the future, in case any member variable naming changes.

import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.Canvas;
import android.os.Build;
import android.text.Layout;
import android.text.SpannableStringBuilder;
import android.util.AttributeSet;
import android.widget.TextView;

import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;

public class EllipsizeTextView extends TextView {
    private static final String THREE_DOTS = "...";
    private static final int THREE_DOTS_LENGTH = THREE_DOTS.length();

    private volatile boolean enableEllipsizeWorkaround = false;
    private SpannableStringBuilder spannableStringBuilder;

    public EllipsizeTextView(Context context) {
        super(context);
    }

    public EllipsizeTextView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public EllipsizeTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public EllipsizeTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

    public void setEnableEllipsizeWorkaround(boolean enableEllipsizeWorkaround) {
        this.enableEllipsizeWorkaround = enableEllipsizeWorkaround;
    }

    // https://stackoverflow.com/questions/14691511/textview-using-spannable-ellipsize-doesnt-work
    // https://blog.csdn.net/htyxz8802/article/details/50387950
    @Override
    protected void onDraw(Canvas canvas) {
        if (enableEllipsizeWorkaround && Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
            final Layout layout = getLayout();

            if (layout.getLineCount() >= getMaxLines()) {
                CharSequence charSequence = getText();
                int lastCharDown = layout.getLineVisibleEnd(getMaxLines()-1);

                if (lastCharDown >= THREE_DOTS_LENGTH && charSequence.length() > lastCharDown) {
                    if (spannableStringBuilder == null) {
                        spannableStringBuilder = new SpannableStringBuilder();
                    } else {
                        spannableStringBuilder.clear();
                    }

                    spannableStringBuilder.append(charSequence.subSequence(0, lastCharDown - THREE_DOTS_LENGTH)).append(THREE_DOTS);
                    setText(spannableStringBuilder);
                }
            }
        }

        super.onDraw(canvas);
    }
}
like image 33
Cheok Yan Cheng Avatar answered Sep 26 '22 23:09

Cheok Yan Cheng


This is a known issue in the Android framework: https://code.google.com/p/android/issues/detail?id=67186

like image 21
Singed Avatar answered Sep 28 '22 23:09

Singed


A simple and working solution

This is my code ->

<TextView
        android:id="@+id/textViewProfileContent"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:singleLine="false"
        android:ellipsize="end"
        android:maxLines="3"
        android:textSize="14sp"
        android:textColor="#000000" />

 SpannableStringBuilder sb = new SpannableStringBuilder();
 SpannableString attrAdditional = new SpannableString(additionalText);
                 attrAdditional.SetSpan(new StyleSpan(TypefaceStyle.Bold), 0, additionalText.Length, 0);...

 sb.Append(attrAdditional);...

 ProfileContent.SetText(sb, **TextView.BufferType.Normal**);

Result

like image 25
biggeek Avatar answered Sep 25 '22 23:09

biggeek


As for single line case, android:maxLines is not working,but android:singleLine is ok.

like image 36
John Avatar answered Sep 25 '22 23:09

John