在TextView中仅对一部分进行省略号处理

8

我想知道是否可以在TextView中仅缩写字符串的一部分。我想要做的是像这样:

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

标题应该被省略,但X必须始终可见。在我的情况下,不能使用多个TextView。您认为有简单的方法来做到这一点吗?

谢谢!

2个回答

12

在项目中,我非常需要一个干净的解决方案。在搜索了一番后,没有找到合适的解决方案,于是我花了些时间编写了这个。

这里提供了一个带有增强省略号控制的TextView实现方式。其原理是使用Android的Spanned接口。它定义了一个枚举,您可以使用它来标记需要在必要时进行省略的特定文本部分。

限制:

  • 不支持在MIDDLE处省略号。如果真的需要(我没有需要),添加应该很容易。
  • 此类将始终将文本呈现在一行上,因为它只支持单行文本。如果需要其他行,则可以扩展它(但这是一个更难的问题)。

以下是用法示例:

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"/>

就这样了。

这是一个结果的示例:

screenshot_of_example

实现

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();
      }
    }
  }
}

2
直到我将layout.getWidth()更改为getMeasuredWidth(),才取得了一些成功。这可能有助于根据你的TextView所在的布局来决定。 - denizmveli

2
你可以尝试使用类似这样的内容:

你可以尝试使用以下内容:

myTextView.setEllipsize(TextUtils.TruncateAt.MIDDLE);

虽然它可能不能完全满足您的需求,但它可能会做出类似于这样的结果:

元素wi...title(X)

参考信息

TruncateAt
setEllipsize


谢谢你的回答!这确实不是一个坏的解决方案,但并不完全符合我的要求... - Antonio
除此之外,您将需要创建自己的检查并在所需位置使用省略号自行绘制。 - Ryan Conrad

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接