TextView с использованием Spannable - эллипсизация не работает

Проблема, которую я пытаюсь исправить, заключается в следующем: у меня есть TextView и я использую Spannable для Spannable некоторых символов жирным шрифтом. Текст должен иметь максимум 2 строки (android:maxLines="2"), и я хочу, чтобы текст был эллипсированным, но по какой-то причине я не могу сделать текст эллипсированным.

Вот простой код:

<?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>

и деятельность:

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 standard dummy ");
        Spannable spannable = (Spannable)name.getText();
        StyleSpan boldSpan = new StyleSpan( Typeface.BOLD );
        spannable.setSpan( boldSpan, 10, 15, Spannable.SPAN_INCLUSIVE_INCLUSIVE );

    }
}

Текст усечен, никаких "..." не отображается. enter image description here

Ответ 1

Я понимаю, что это очень старый пост, но, видя, что он все еще остается без ответа, и я также столкнулся с этим вопросом сегодня, я думал, что опубликую решение для этого. Надеюсь, это поможет кому-то в будущем.

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

Ответ 2

Имея такую же проблему и, похоже, для меня работают следующие работы:

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

в xml textView имеет android:multiLine set и android:ellipsize="end", android:singleLine="false;

Ответ 3

Вы правы в этом эллипсисе, объявленном либо в xml, либо в коде, который не будет работать с текстом с паутиной.

Тем не менее, с небольшим количеством расследований вы можете фактически сделать эллипсинг самостоятельно:

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

}

Ответ 4

Это может немного обмануть, используя отражение, чтобы решить эту проблему. После прочтения исходного кода AOSP в TextView.java DynamicLayout содержит только статический член поля с именем sStaticLayout и он создан новым StaticLayout (null) без каких-либо параметров, включая maxLines.

Следовательно, doEllipsis всегда будет false, поскольку mMaximumVisibleLineCount по умолчанию задает Integer.MAX_VALUE.

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

Поэтому я расширяю TextView и создаю представление с именем 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();
        }
    }
}

}

задача решена!

Ответ 6

Простое и эффективное решение

Это мой код ->

<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**);

результат

Ответ 7

enter image description here

https://github.com/lsjwzh/FastTextView имеет для этого чистое разрешение. Сначала необходимо обернуть свою натянутую строку, затем переопределить getSpans, getSpanStart, getSpanEnd... https://github.com/lsjwzh/FastTextView/blob/master/widget.FastTextView/src/main/java/android/text/EllipsisSpannedContainer. Ява

Строка 207 показывает, как использовать. https://github.com/lsjwzh/FastTextView/blob/master/widget.FastTextView/src/main/java/com/lsjwzh/widget/text/FastTextView.java

Вы также можете использовать FastTextView вместо Android TextView

Ответ 8

Другое решение - переписать onDraw для TextView. Следующее предложенное решение не использует никакой техники отражения. Следовательно, не должно нарушаться в будущем, в случае изменения имен переменных членов.

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

Ответ 9

Что касается однострочного случая, android: maxLines не работает ,, но android: singleLine в порядке.

Ответ 10

Попробуйте это для меня.

Это мой текстовый вид

 <TextView
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:maxLines="2"
         android:ellipsize="end"
         android:text="Type here maximum characters."
         android:textSize="16sp" />

enter image description here