Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ellipsize only a section in a TextView

I was wondering if it is possible to abbreviate only a portion of a string in a TextView. What I would like to do is something like this:

Element with short title (X)
Element with a very lo...(X)

The title should be ellipsized, but the X must be always visible. In my case, is not possible to use more than one TextView. Do you think there is a simple way of doing this?

Thanks!

like image 556
Antonio Avatar asked Jul 12 '10 12:07

Antonio


2 Answers

I really needed a clean solution for a project so after searching around and not finding any solutions I felt I liked, I took some time to write this up.

Here is an implementation of a TextView with enhanced ellipsis control. The way it works is by using Android's Spanned interface. It defines an enum you can use to tag the specific section of text you'd like to be ellipsized if needed.

Limitations:

  • Does not support ellipsis at MIDDLE. This should be easy to add if it's really needed (I didn't).
  • This class will always render the text onto one line, as it only supports a single line of text. Others are welcome to extend it if that's needed (but it's a far harder problem).

Here's a sample of the usage:

FooActivity.java

class FooActivity extends Activity {

  /**
   * You can do this however you'd like, this example uses this simple
   * helper function to create a text span tagged for ellipsizing
   */
  CharSequence ellipsizeText(String text) {
    SpannableString s = new SpannableString(text);
    s.setSpan(TrimmedTextView.EllipsizeRange.ELLIPSIS_AT_END, 0, s.length(),
      Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
    return s;
  }

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.foo_layout);
    TextView textView = (TextView) findViewById(R.id.textView4);
    SpannableStringBuilder text = new SpannableStringBuilder();
    text.append(ellipsizeText("This is a long string of text which has important information "));
    text.append("AT THE END");
    textView.setText(text);
  }
}

res/layouts/foo_layout.xml

<com.example.text.TrimmedTextView
  android:id="@+id/textView4"
  android:layout_width="match_parent"
  android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceLarge"/>

That's it

Here's an example of the result:

screenshot_of_example

The Implementation

package com.example.text;

import android.content.Context;
import android.text.Editable;
import android.text.Layout;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.TextUtils;
import android.text.TextUtils.TruncateAt;
import android.text.TextWatcher;
import android.util.AttributeSet;
import android.util.Log;
import android.widget.TextView;

public class TrimmedTextView extends TextView {
  public static enum EllipsizeRange {
    ELLIPSIS_AT_START, ELLIPSIS_AT_END;
  }

  private CharSequence originalText;
  private SpannableStringBuilder builder = new SpannableStringBuilder();

  /**
   * This allows the cached value of the original unmodified text to be
   * invalidated whenever set externally.
   */
  private final TextWatcher textCacheInvalidator = new TextWatcher() {
    @Override
    public void onTextChanged(CharSequence s, int start, int before, int count) {
      originalText = null;
    }

    @Override
    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
    }

    @Override
    public void afterTextChanged(Editable s) {
    }
  };

  public TrimmedTextView(Context context) {
    this(context, null, 0);
  }

  public TrimmedTextView(Context context, AttributeSet attrs) {
    this(context, attrs, 0);
  }

  public TrimmedTextView(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
    addTextChangedListener(textCacheInvalidator);
    Log.v("TEXT", "Set!");
  }

  /**
   * Make sure we return the original unmodified text value if it's been
   * custom-ellipsized by us.
   */
  public CharSequence getText() {
    if (originalText == null) {
      return super.getText();
    }
    return originalText;
  }

  @Override
  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    Layout layout = getLayout();
    CharSequence text = layout.getText();
    if (text instanceof Spanned) {
      Spanned spanned = (Spanned) text;
      int ellipsisStart;
      int ellipsisEnd;
      TruncateAt where = null;
      ellipsisStart = spanned.getSpanStart(EllipsizeRange.ELLIPSIS_AT_START);
      if (ellipsisStart >= 0) {
        where = TruncateAt.START;
        ellipsisEnd = spanned.getSpanEnd(EllipsizeRange.ELLIPSIS_AT_START);
      } else {
        ellipsisStart = spanned.getSpanStart(EllipsizeRange.ELLIPSIS_AT_END);
        if (ellipsisStart >= 0) {
          where = TruncateAt.END;
          ellipsisEnd = spanned.getSpanEnd(EllipsizeRange.ELLIPSIS_AT_END);
        } else {
          // No EllipsisRange spans in this text
          return;
        }
      }

      Log.v("TEXT", "ellipsisStart: " + ellipsisStart);
      Log.v("TEXT", "ellipsisEnd:   " + ellipsisEnd);
      Log.v("TEXT", "where:         " + where);

      builder.clear();
      builder.append(text, 0, ellipsisStart).append(text, ellipsisEnd, text.length());
      float consumed = Layout.getDesiredWidth(builder, layout.getPaint());
      CharSequence ellipsisText = text.subSequence(ellipsisStart, ellipsisEnd);
      CharSequence ellipsizedText = TextUtils.ellipsize(ellipsisText, layout.getPaint(),
          layout.getWidth() - consumed, where);
      if (ellipsizedText.length() < ellipsisText.length()) {
        builder.clear();
        builder.append(text, 0, ellipsisStart).append(ellipsizedText)
            .append(text, ellipsisEnd, text.length());
        setText(builder);
        originalText = text;
        requestLayout();
        invalidate();
      }
    }
  }
}
like image 87
Mark Renouf Avatar answered Sep 29 '22 18:09

Mark Renouf


You can try using something like this:

myTextView.setEllipsize(TextUtils.TruncateAt.MIDDLE);

It might not give you exactly what you want though, it may do something like this:

Element wi...title (X)

Reference Info

TruncateAt
setEllipsize

like image 35
Ryan Conrad Avatar answered Sep 29 '22 18:09

Ryan Conrad