Ellipsize individual lines of a SpannableString in a TextView
Asked Answered
M

3

7

I have a TextView storing an address that is two lines long. The street goes on the first line and is bold. The city & state go on the second line and are not bold. I want the end of each line ellipsized if the individual text on that line runs over. I'm using a SpannableString to store the address because I want the street address to be bold and the city & state to be not bold.

Is there a way to do this that isn't a total hack without using two TextViews for each line?

Example outputs:

Ex:
123812 Washington A...
Schenectady New Yor...

Ex:
2792 Dantzler Boulev...
Charleston SC, 29406

Ex:
3 Main Street
Atlanta GA

Mutule answered 3/4, 2013 at 22:0 Comment(0)
D
22

I had the same problem and solved it by creating an EllipsizeLineSpan class. You can wrap each line that you want to ellipsize with it.

Example for marking up a spannable string with it:

SpannableStringBuilder textspan = new SpannableStringBuilder("#1.Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\n"+
    "Protect from ellipsizing #2.Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\n"+
    "#3.Lorem ipsum dolor sit amet, consectetur adipisicing elit\n"+
    "#4.Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\n");

// find  ellipsizable text (from '#' to newline)
Pattern pattern = Pattern.compile("#.*\\n", Pattern.CASE_INSENSITIVE);
Matcher matcher = pattern.matcher(textspan);
while(matcher.find()) {
    textspan.setSpan(new EllipsizeLineSpan(), matcher.start(), matcher.end(),   Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}

EllipsizeLineSpan:

public class EllipsizeLineSpan extends ReplacementSpan implements LineBackgroundSpan {
int layoutLeft = 0;
int layoutRight = 0;

public EllipsizeLineSpan () {
}

@Override
public void drawBackground (Canvas c, Paint p,
                            int left, int right,
                            int top, int baseline, int bottom,
                            CharSequence text, int start, int end,
                            int lnum) {
    Rect clipRect = new Rect();
    c.getClipBounds(clipRect);
    layoutLeft = clipRect.left;
    layoutRight = clipRect.right;
}

@Override
public int getSize (Paint paint, CharSequence text, int start, int end,
                    Paint.FontMetricsInt fm) {
    return layoutRight - layoutLeft;
}

@Override
public void draw (Canvas canvas, CharSequence text, int start, int end,
                  float x, int top, int y, int bottom, Paint paint) {
    float textWidth = paint.measureText(text, start, end);

    if (x + (int) Math.ceil(textWidth) < layoutRight) {  //text fits
        canvas.drawText(text, start, end, x, y, paint);
    } else {
        float ellipsiswid = paint.measureText("\u2026");
        // move 'end' to the ellipsis point
        end = start + paint.breakText(text, start, end, true, layoutRight - x - ellipsiswid, null);
        canvas.drawText(text, start, end, x, y, paint);
        canvas.drawText("\u2026", x + paint.measureText(text, start, end), y, paint);

    }

}


}
Disease answered 23/8, 2013 at 20:57 Comment(3)
Is it possible to add "Protect from ellipsizing" text to the end of line?Karlotta
Assuming that you have a char prefix like the # (say '^' ), then you would need to measure the text from after the '^' and subtract the measured value in the calculation the moves the 'end' point. Then do a last drawtext() for the remaining text after the '^'Disease
Good pattern for me is : Pattern.compile(".*\\n", Pattern.CASE_INSENSITIVE);Bickel
D
1

DangVarmit's answer helped me a lot but after using it for a while, I found out that sometimes the top offset of the text is randomly incorrect. Here is the part that needs to be replaced:

  override fun getSize(paint: Paint, text: CharSequence, start: Int, end: Int, fontMetricsInt: Paint.FontMetricsInt?): Int {
    fontMetricsInt?.let {
        it.top = paint.getFontMetricsInt(it)
    }
    return Math.round(paint.measureText(text, start, start))
}
Durmast answered 20/2, 2018 at 18:24 Comment(0)
A
-4

Try to set TextView ellipse size in your layout like:

 android:ellipsize="end"
Appreciative answered 3/4, 2013 at 22:17 Comment(2)
but that will just ellipsize the end of the entire TextView. I want both lines ellipsized.Mutule
@Mutule I don't think it's possible with a single TextView, put in severalTextViews is a trick.Appreciative

© 2022 - 2024 — McMap. All rights reserved.