设置文本视图省略号并在末尾添加“查看更多”

32

我正在尝试设置文本视图的省略号。使用以下代码。我希望在3个点后的截断字符串的末尾添加“查看更多”。如果同一文本视图可以实现这一点,那将是很好的,或者“查看更多”也可以在单独的文本视图中工作。最大允许行数为4。我尝试设置第一个文本视图的宽度,但它在前3行末留下了空白空间。请参见下面的图像。

<RelativeLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content" >

    <TextView
        android:id="@+id/tvReviewDescription"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_alignParentTop="true"
        android:maxLines="4"
        android:text="I tend to shy away from restaurant chains, but wherever I go, PF Chang&apos;s has solidly good food and, like Starbucks, they&apos;re reliable. We were staying in Boston for a week and after a long day and blah blah blah blah... "
        android:textColor="@color/black"
        android:textSize="13dp" 
        android:maxLength="280"
        android:ellipsize="end"/>

    <TextView
        android:id="@+id/textView1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignBottom="@+id/tvReviewDescription"
        android:layout_alignParentRight="true"
        android:text="@string/label_view_more"
        android:textColor="@color/yellow" />
</RelativeLayout>

输入图像描述


你会手动提供这个内容吗?比如:“我倾向于回避…” 如果是的话,显然你必须控制你的内容不要超出或者你会给一个列表视图吗? - Neha Gupta
@Neha,你能通过代码片段详细说明一下吗?他想要显示4行,如果超过4行,则截断文本并显示“查看更多”,请提供一些处理多个屏幕的建议。如何手动处理? - Nauman Zubair
如果您点击“查看更多”,会发生什么?这段文字会展开还是跳转到新屏幕? - Jitender Dev
你找到了任何答案吗,@Basit ZIa? - Noman
18个回答

33

寻找我的回答

   public static void makeTextViewResizable(final TextView tv, final int maxLine, final String expandText, final boolean viewMore) {

    if (tv.getTag() == null) {
        tv.setTag(tv.getText());
    }
    ViewTreeObserver vto = tv.getViewTreeObserver();
    vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {

        @SuppressWarnings("deprecation")
        @Override
        public void onGlobalLayout() {

            ViewTreeObserver obs = tv.getViewTreeObserver();
            obs.removeGlobalOnLayoutListener(this);
            if (maxLine == 0) {
                int lineEndIndex = tv.getLayout().getLineEnd(0);
                String text = tv.getText().subSequence(0, lineEndIndex - expandText.length() + 1) + " " + expandText;
                tv.setText(text);
                tv.setMovementMethod(LinkMovementMethod.getInstance());
                tv.setText(
                        addClickablePartTextViewResizable(Html.fromHtml(tv.getText().toString()), tv, maxLine, expandText,
                                viewMore), TextView.BufferType.SPANNABLE);
            } else if (maxLine > 0 && tv.getLineCount() >= maxLine) {
                int lineEndIndex = tv.getLayout().getLineEnd(maxLine - 1);
                String text = tv.getText().subSequence(0, lineEndIndex - expandText.length() + 1) + " " + expandText;
                tv.setText(text);
                tv.setMovementMethod(LinkMovementMethod.getInstance());
                tv.setText(
                        addClickablePartTextViewResizable(Html.fromHtml(tv.getText().toString()), tv, maxLine, expandText,
                                viewMore), TextView.BufferType.SPANNABLE);
            } else {
                int lineEndIndex = tv.getLayout().getLineEnd(tv.getLayout().getLineCount() - 1);
                String text = tv.getText().subSequence(0, lineEndIndex) + " " + expandText;
                tv.setText(text);
                tv.setMovementMethod(LinkMovementMethod.getInstance());
                tv.setText(
                        addClickablePartTextViewResizable(Html.fromHtml(tv.getText().toString()), tv, lineEndIndex, expandText,
                                viewMore), TextView.BufferType.SPANNABLE);
            }
        }
    });

}

private static SpannableStringBuilder addClickablePartTextViewResizable(final Spanned strSpanned, final TextView tv,
                                                                        final int maxLine, final String spanableText, final boolean viewMore) {
    String str = strSpanned.toString();
    SpannableStringBuilder ssb = new SpannableStringBuilder(strSpanned);

    if (str.contains(spanableText)) {


        ssb.setSpan(new MySpannable(false){
            @Override
            public void onClick(View widget) {
                if (viewMore) {
                    tv.setLayoutParams(tv.getLayoutParams());
                    tv.setText(tv.getTag().toString(), TextView.BufferType.SPANNABLE);
                    tv.invalidate();
                    makeTextViewResizable(tv, -1, "See Less", false);
                } else {
                    tv.setLayoutParams(tv.getLayoutParams());
                    tv.setText(tv.getTag().toString(), TextView.BufferType.SPANNABLE);
                    tv.invalidate();
                    makeTextViewResizable(tv, 3, ".. See More", true);
                }
            }
        }, str.indexOf(spanableText), str.indexOf(spanableText) + spanableText.length(), 0);

    }
    return ssb;

}

另一个类:

import android.graphics.Color;
import android.text.TextPaint;
import android.text.style.ClickableSpan;
import android.view.View;

public class MySpannable extends ClickableSpan {

private boolean isUnderline = true;

/**
 * Constructor
 */
public MySpannable(boolean isUnderline) {
    this.isUnderline = isUnderline;
}

@Override
public void updateDrawState(TextPaint ds) {
    ds.setUnderlineText(isUnderline);
    ds.setColor(Color.parseColor("#1b76d3"));
}

@Override
public void onClick(View widget) {


 }
}

最后一步是调用它:

DetailTv.setText(discription);
makeTextViewResizable(DetailTv, 3, "See More", true);

2
RecycleView的项目在滚动时被刷新,这会导致问题。 - Tarun Sharma
1
有人在RecyclerView中使用过这个吗? - Dilip
@akansha-rathore,你的代码在API 24+中可以正常工作,但对于较低版本来说不太可靠。我已经按照所有步骤进行操作了。 - abhishek
我不确定,但这个答案没有处理链接、表情符号或者话题标签。在我的情况下,我不能在URL或者话题标签的中间进行切割:www.google.de = www.go see more 或者 #germany = #germ see more - A. Amini

27
比接受答案更简单:
public static final int MAX_LINES = 3;
public static final String TWO_SPACES = " "; 

String myReallyLongText = "Bacon ipsum dolor amet porchetta venison ham fatback alcatra tri-tip, turducken strip steak sausage rump burgdoggen pork loin. Spare ribs filet mignon salami, strip steak ball tip shank frankfurter corned beef venison. Pig pork belly pork chop andouille. Porchetta pork belly ground round, filet mignon bresaola chuck swine shoulder leberkas jerky boudin. Landjaeger pork chop corned beef, tri-tip brisket rump pastrami flank."

textView.setText(myReallyLongText);
textView.post(new Runnable() {
                @Override
                public void run() {
                    // Past the maximum number of lines we want to display.
                    if (textView.getLineCount() > MAX_LINES) {
                        int lastCharShown = textView.getLayout().getLineVisibleEnd(MAX_LINES - 1);

                        textView.setMaxLines(MAX_LINES);

                        String moreString = context.getString(R.string.more);
                        String suffix = TWO_SPACES + moreString;
                        
                        // 3 is a "magic number" but it's just basically the length of the ellipsis we're going to insert
                        String actionDisplayText = myReallyLongText.substring(0, lastCharShown - suffix.length() - 3) + "..." + suffix;

                        SpannableString truncatedSpannableString = new SpannableString(actionDisplayText);
                        int startIndex = actionDisplayText.indexOf(moreString);
                        truncatedSpannableString.setSpan(new ForegroundColorSpan(context.getColor(android.R.color.blue)), startIndex, startIndex + moreString.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
                        textView.setText(truncatedSpannableString);
                    }
                }
            });

如果你想让文本的“查看更多”部分可点击(而不是整个TextView),请按照这里在StackOverflow中关于如何设置文本视图的部分可点击所概述的使用ClickableSpan。我要提醒你注意这样做的用户体验影响,通常截断文本是因为有很多文本内容且空间有限,所以字体大小可能已经很小了。对于用户来说,点击导航到完整文本的小目标可能不是最好或最易于使用的用户体验,特别是如果你的用户年龄较大或有行动不便的问题,使得点击屏幕的小部分困难。因此,一般情况下我建议将整个TextView设置为可点击,而不是其一小部分。

作为替代方案,你可以像我一样将其变成一个自定义视图。以下是类的代码,你可以使用ClickableSpan代码进行修改,但是由于我很长时间没有编译这个项目了,所以我不希望做出需要验证是否安全发布的更改。如果有人想尝试,请欢迎编辑。

public class TruncatingTextView extends AppCompatTextView {
    private static final String TWO_SPACES = "  ";

    private int truncateAfter = Integer.MAX_VALUE;

    private String suffix;
    private final RelativeSizeSpan truncateTextSpan = new RelativeSizeSpan(0.75f);
    private ForegroundColorSpan viewMoreTextSpan;
    private final String moreString = getContext().getString(R.string.more);

    private final String ellipsis = getContext().getString(R.string.ellipsis);

    public TruncatingTextView(Context context) {
        super(context);
        init();
    }

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

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

    private void init() {
        viewMoreTextSpan = new ForegroundColorSpan(ContextCompat.getColor(getContext(), R.color.upsell_blue));
    }

    public void setText(CharSequence fullText, @Nullable CharSequence afterTruncation, int truncateAfterLineCount) {
        this.suffix = TWO_SPACES + moreString;

        if (!TextUtils.isEmpty(afterTruncation)) {
            suffix += TWO_SPACES + afterTruncation;
        }

        if (this.truncateAfter != truncateAfterLineCount) {
            this.truncateAfter = truncateAfterLineCount;
            setMaxLines(truncateAfter);
        }

        setText(fullText);
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);

        if (getLayout() != null && getLayout().getLineCount() > truncateAfter) {
            int lastCharToShowOfFullTextAfterTruncation = getLayout().getLineVisibleEnd(truncateAfter - 1) - suffix.length() - ellipsis.length();

            int startIndexOfMoreString = lastCharToShowOfFullTextAfterTruncation + TWO_SPACES.length() + 1;

            SpannableString truncatedSpannableString = new SpannableString(getText().subSequence(0, lastCharToShowOfFullTextAfterTruncation) + ellipsis + suffix);
            truncatedSpannableString.setSpan(truncateTextSpan, startIndexOfMoreString, truncatedSpannableString.length(), Spannable.SPAN_EXCLUSIVE_INCLUSIVE);
            truncatedSpannableString.setSpan(viewMoreTextSpan, startIndexOfMoreString, startIndexOfMoreString + moreString.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
            setText(truncatedSpannableString);
        }
    }
}

1
这里的“TWO_SPACES”是什么? - Hardik Joshi
1
字面上是两个空格的常量。public static final String TWO_SPACES = " "; - Chantell Osejo
2
根据我的经验实现 - 这个答案对我帮助很大,而且在我看来是一个非常直接简单的解决方案。 - MikeOscarEcho
2
如何使视图更具可点击性? - Akshay Rajput
2
更简单,因为您无需单击处理。 - philtz
显示剩余5条评论

14

这可以在运行时实现,您只需检查字符串的长度并在字符串末尾添加带下划线的“查看更多”视图,就像这样。

我以长度“20”作为示例,您可以根据需要进行更改。

final TextView result = (TextView) findViewById(R.id.textview);

String text = "I tend to shy away from restaurant chains, but wherever I go, PF Chang&apos;s has solidly good food and, like Starbucks, they&apos;re reliable. We were staying in Boston for a week and after a long day and blah blah blah blah...";

if (text.length()>20) {
    text=text.substring(0,20)+"...";
    result.setText(Html.fromHtml(text+"<font color='red'> <u>View More</u></font>"));       

}

3
如果更改设备方向,则可能能够显示完整的文本,因此“查看更多”按钮不再必要。我认为您的解决方案既不是“字体大小”也不是屏幕分辨率无关的。 - jmhostalet
谢谢分享代码片段。请问如何执行“查看更多”文本的点击事件? - Sneha Patel
尽管我认为这不是一个好的解决方案,因为它依赖于特定的屏幕尺寸等,但设置事件监听很容易,因为这是一个TextView,只需让用户的大手指在TextView上的任何位置点击即可。 - Brill Pappin
谢谢您的回答。在此基础上,我已经发布了一个带有“查看更多”和“显示更少”文本的点击事件监听器的答案。 - Android Developer

6

这将产生省略效果。

设置布尔值isCheck为true;

将此放入xml中:

<TextView
        android:id="@+id/txt_id"
        android:maxLines="2"
        android:ellipsize="end"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>

并且代码:

txt_id= (TextView)findViewById(R.id.txt_id);
txt_id.setText("data");
txt_id.setOnClickListener(new View.OnClickListener() {
     @Override
     public void onClick(View view) {
                   if (isCheck) {
                      txt_id.setMaxLines(10);
                      isCheck = false;
                   } else {
                      txt_id.setMaxLines(2);
                      isCheck = true;
           }
       }
  }

4

请查看我的库:https://github.com/AhmMhd/SeeMoreTextView-Android

<com.abdulhakeem.seemoretextview.SeeMoreTextView
    android:id="@+id/textview"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
/>

用法:

TextView seemoreTv = (TextView) findViewById(R.id.textview)

seemoreTv.setContent("some really long text here.")

它在RecyclerView上也表现出色。


嗨,阿卜杜勒,谢谢你。你救了我的一天 :-) 祝好运! - Riddhi Shah

3
在 Kotlin 适配器中,你可以像这样在 TextView 上写 "查看更多"。
if (News[position].description.length > 150) {
  holder.desc.text = Html.fromHtml(News[position].description.substring(0, 150) + "..." + "<font color='blue'> <u>View More</u></font>")
} else {
   holder.desc.text = News[position].description
}
holder.desc.setOnClickListener {
   if (holder.desc.text.toString().endsWith("View More")) {
      holder.desc.text = News[position].description
   } else {
      if (News[position].description.length > 150) {
        holder.desc.text = Html.fromHtml(News[position].description.substring(0, 150) + "..." + "<font color='blue'> <u>View More</u></font>")
      } else holder.desc.text = News[position].description
   }
}

非常好的答案。谢谢。 - Adarsh Vijayan P

2

这个解决方案在代码实现上比较容易。它不太支持即时更改,但可以轻松修改以实现此功能。

public class ExpandableTextView extends TextView {

    private final String readMoreText = "...read more";
    private final int readMoreColor = Color.parseColor("#4A0281");

    private int _maxLines = 4;
    private CharSequence originalText;

    public ExpandableTextView(Context context) {
        super(context);
        init(context);
    }

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

    public ExpandableTextView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init(context);
    }

    private void init(Context context) {

        ViewTreeObserver vto = getViewTreeObserver();
        vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {

            @SuppressWarnings("deprecation")
            @Override
            public void onGlobalLayout() {

                ViewTreeObserver obs = getViewTreeObserver();
                obs.removeGlobalOnLayoutListener(this);

                truncateText();
            }
        });
    }

    @Override
    public void setText(CharSequence text, BufferType type) {
        super.setText(text, type);

        if (originalText == null) {
            originalText = text;
        }
    }

    @Override
    public int getMaxLines() {
        return _maxLines;
    }

    @Override
    public void setMaxLines(int maxLines) {
        _maxLines = maxLines;
    }

    public void truncateText() {

        int maxLines = _maxLines;
        String text = getText().toString();

        if (getLineCount() >= maxLines) {

            int lineEndIndex = getLayout().getLineEnd(maxLines - 1);

            String truncatedText = getText().subSequence(0, lineEndIndex - readMoreText.length() + 1) + readMoreText;

            Spannable spannable = new SpannableString(truncatedText);
            spannable.setSpan(new ForegroundColorSpan(readMoreColor), truncatedText.length() - readMoreText.length(), truncatedText.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);

            setText(spannable, TextView.BufferType.SPANNABLE);

            super.setMaxLines(_maxLines);
        }
    }

    public void expandText() {
        setText(originalText);
        super.setMaxLines(1000);
    }

    public void reset() {
        originalText = null;
    }
}

谢谢,但我无法点击“阅读更多”文本来展开文本。 - Sana Ebadi

2
在上述解决方案中,如果文本长度小于最大行数,则显示扩展文本(查看更多/查看更少)。在这个类中,我修复了这个错误。您只需要将这个类放入您的代码中,并在XML中使用它。您可以根据自己的要求轻松修改它(扩展文本的颜色、字体样式等)。
      public class ExpandableTextView extends AppCompatTextView {

    private static Context context;
    private TextView textView;
    private int maxLine = 3;
    private boolean isViewMore = true;

    public ExpandableTextView(Context context) {
        super(context);
        ExpandableTextView.context = context;
        textView = this;
        initViews();
    }

    public ExpandableTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
        ExpandableTextView.context = context;
        textView = this;
        initViews();
    }

    public ExpandableTextView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        ExpandableTextView.context = context;
        textView = this;
        initViews();
    }

    public void initViews() {
        if (textView.getText().toString().isEmpty()) {
            return;
        }
        if (textView.getTag() == null) {
            textView.setTag(textView.getText());
        }
        textView.setTypeface(Typeface.createFromAsset(context.getAssets(), "GothamBook.ttf"));
        ViewTreeObserver vto = textView.getViewTreeObserver();
        vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @SuppressWarnings("deprecation")
            @Override
            public void onGlobalLayout() {
                String text, expandText = "See ";
                int lineEndIndex;
                ViewTreeObserver obs = textView.getViewTreeObserver();
                obs.removeGlobalOnLayoutListener(this);
                int lineCount = textView.getLayout().getLineCount();

                expandText += isViewMore ? "More" : "Less";
                if (lineCount <= maxLine) {
                    lineEndIndex = textView.getLayout().getLineEnd(textView.getLayout().getLineCount() - 1);
                    text = textView.getText().subSequence(0, lineEndIndex).toString();
                } else if (isViewMore && maxLine > 0 && textView.getLineCount() >= maxLine) {
                    lineEndIndex = textView.getLayout().getLineEnd(maxLine - 1);
                    text = textView.getText().subSequence(0, lineEndIndex - expandText.length() + 1) + " " + expandText;
                } else {
                    lineEndIndex = textView.getLayout().getLineEnd(textView.getLayout().getLineCount() - 1);
                    text = textView.getText().subSequence(0, lineEndIndex) + " " + expandText;
                }
                textView.setText(text);
                textView.setMovementMethod(LinkMovementMethod.getInstance());
                if (lineCount > maxLine)
                    textView.setText(addClickablePartTextViewResizable(expandText),
                            BufferType.SPANNABLE);
                textView.setSelected(true);
            }
        });
    }

    private SpannableStringBuilder addClickablePartTextViewResizable(final String expandText) {
        String string = textView.getText().toString();
        SpannableStringBuilder expandedStringBuilder = new SpannableStringBuilder(string);
        if (string.contains(expandText)) {
            expandedStringBuilder.setSpan(new ClickableSpan() {
                @Override
                public void onClick(View widget) {
                    textView.setLayoutParams(textView.getLayoutParams());
                    textView.setText(textView.getTag().toString(), BufferType.SPANNABLE);
                    textView.invalidate();
                    maxLine = isViewMore ? -1 : 3;
                    isViewMore = !isViewMore;
                    initViews();
                }

                @Override
                public void updateDrawState(@NonNull TextPaint ds) {
                    ds.setUnderlineText(true);
                    ds.setColor(context.getResources().getColor(R.color.red));
                    ds.setTypeface(Typeface.createFromAsset(context.getAssets(), "GothamMedium.ttf"));
                }
            }, string.indexOf(expandText), string.length(), 0);
        }
        return expandedStringBuilder;
    }
}

如果您设置了动态数据,您需要在将文本设置到文本视图之后调用initViews()。
tvDescription.setText(sessionModel.getDescription());
tvDescription.initViews();

1
我已经在RecyclerView中使用了这个,它完美地运作了。谢谢兄弟。 - Erick
在回收视图中,它只在第一次起作用。但是后来当向下滚动和向上滚动第二次时,它就不起作用了。 - Aditya Patil
@AdityaPatil,正如您所看到的,此类中使用的所有变量都是局部变量。因此,当您滚动回收视图时,它们会重置,因为它会重新创建所有视图。 您必须从列表数据中管理isViewMore变量。 - Nitish Nanda

1
感谢Jitender的答案。在此基础上,我根据文本长度做了以下实现。如果您想要在指定行数后精确添加“查看更多”选项,则此解决方案可能不是理想的解决方案,但是假设单行大约有50个字符,则该解决方案将在您可以调整行数的情况下很好地工作。如果文本长度大于150,则以下解决方案将添加“查看更多”选项,并将文本省略为150个字符。单击“查看更多”后,它将显示完整的文本,并显示“显示较少”选项。再次单击“显示较少”,它将省略文本为150个字符。无需单独的视图。此外,它与recyclerview项目的textview很好地配合使用。
if(inputText.length()>150)
        {
            String text=inputText.substring(0,150)+"...";
            final String fulltext=inputText;

            final SpannableString ss = new SpannableString(text+"View More");

            ClickableSpan span1 = new ClickableSpan() {
                @Override
                public void onClick(View textView) {
                    // do some thing
                    SpannableString ss1 = new SpannableString(fulltext+"Show Less");
                    ClickableSpan span2 = new ClickableSpan() {
                        @Override
                        public void onClick(View textView) {
                            // do some thing
                            textView.setText(ss);
                            textView.setMovementMethod(LinkMovementMethod.getInstance());

                        }
                    };
                    ss1.setSpan(span2, fulltext.length(), ss1.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
                    ss1.setSpan(new ForegroundColorSpan(Color.BLUE), fulltext.length(), ss1.length(),
                            Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);


                    textView.setText(ss1);
                    textView.setMovementMethod(LinkMovementMethod.getInstance());
                }
            };
            ss.setSpan(span1, 153, 162, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
            ss.setSpan(new ForegroundColorSpan(Color.BLUE), 153,162,
                    Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);

            textView.setText(ss);
            textView.setMovementMethod(LinkMovementMethod.getInstance());
        }
else
        {
            textView.setText(inputText);
        }

1

建议使用这个库 :)

//将此依赖项添加到应用程序 Gradle 中

implementation 'com.borjabravo:readmoretextview:2.1.0'

使用方法:

<com.borjabravo.readmoretextview.ReadMoreTextView
   android:id="@+id/text2"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   android:layout_marginTop="@dimen/activity_vertical_margin"
   android:text="@string/DemoText"
   app:colorClickableText="#3F51B5"/>

请查看此链接:https://github.com/bravoborja/ReadMoreTextView


有没有选项可以仅将点击监听器设置为“阅读更多”文本? - viper

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