使用SpannableStringBuilder注意事项

需求中有一行文字显示的大小、颜色、下划线、加粗等不同,可以用几个TextView实现,也可以用SpannableStringBuilder在一个TextView上实现,还可以在string的文件中用H5的语法写。

后两种的差别主要在点击事件上,用H5的语法没法对一个字符串中的几个字设置点击事件。

使用时把多个字符串用append()方法拼接上,设置需要用到的Span。

  • 设置Span时要注意按照顺序
  • 设置颜色、点击事件时Span对象不能复用,用几个就要创建几个
  • 对ClickableSpan默认有下划线,可以重写updateDrawState方法去掉
  • 用ClickableSpan时,除了用setText,还要用lawAndPrivacyTv.setMovementMethod(LinkMovementMethod.getInstance())否则点击事件不生效
  • 另,重写的updateDrawState方法里也可以设置默认颜色(setColor)

Spannable.SPAN_EXCLUSIVE_EXCLUSIVE:前后都不包括,即在指定范围的前面和后面插入新字符都不会应用新样式 Spannable.SPAN_EXCLUSIVE_INCLUSIVE:前面不包括,后面包括。即仅在范围字符的后面插入新字符时会应用新样式 Spannable.SPAN_INCLUSIVE_EXCLUSIVE:前面包括,后面不包括。 Spannable.SPAN_INCLUSIVE_INCLUSIVE:前后都包括。

源码:

private void initLawAndPrivacyTv() {
    int position = 0;
    String agree = Utils.getString(R.string.text_agree);
    String privacy = Utils.getString(R.string.privacy_policy);
    String and = Utils.getString(R.string.text_and);
    String law = Utils.getString(R.string.law_policy);

    ForegroundColorSpan agreeBlackSpan = new ForegroundColorSpan(Color.BLACK);
    ForegroundColorSpan andBlackSpan = new ForegroundColorSpan(Color.BLACK);
    ForegroundColorSpan privacyBlueSpan = new ForegroundColorSpan(Utils.getColor(R.color.color_3091F2));
    ForegroundColorSpan lawBlueSpan = new ForegroundColorSpan(Utils.getColor(R.color.color_3091F2));

    ClickableSpan privacyClickableSpan = new ClickableSpan() {
        @Override
        public void onClick(@NonNull View widget) {
            jumpToPrivacyPolicy();
        }

        @Override
        public void updateDrawState(@NonNull TextPaint textPaint) {
            super.updateDrawState(textPaint);
            textPaint.setUnderlineText(false);
        }
    };
    ClickableSpan lawClickableSpan = new ClickableSpan() {
        @Override
        public void onClick(@NonNull View widget) {
            JumpToLawPolicy();
        }

        @Override
        public void updateDrawState(@NonNull TextPaint textPaint) {
            super.updateDrawState(textPaint);
            textPaint.setUnderlineText(false);
        }
    };

    SpannableStringBuilder stringBuilder = new SpannableStringBuilder();
    stringBuilder.append(agree);
    stringBuilder.setSpan(agreeBlackSpan, 0, stringBuilder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
    position += agree.length();
    stringBuilder.append(law);
    stringBuilder.setSpan(lawBlueSpan, position, stringBuilder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
    stringBuilder.setSpan(lawClickableSpan, position, stringBuilder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
    position += law.length();
    stringBuilder.append(and);
    stringBuilder.setSpan(andBlackSpan, position, stringBuilder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
    position += and.length();
    stringBuilder.append(privacy);
    stringBuilder.setSpan(privacyBlueSpan, position, stringBuilder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
    stringBuilder.setSpan(privacyClickableSpan, position, stringBuilder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
    lawAndPrivacyTv.setText(stringBuilder);
// 不设置点击事件不起作用
lawAndPrivacyTv.setMovementMethod(LinkMovementMethod.getInstance());
}

除了SpannableStringBuilder,还有个SpannableString,区别和String与StringBuilder的区别相同。

补充:

四种flag在按照顺序拼接文字时没有区别,区别在SpannableStringBuilder#insert时,如下(x为增加的):

SPAN_INCLUSIVE_INCLUSIVE

SPAN_INCLUSIVE_EXCLUSIVE

SPAN_EXCLUSIVE_INCLUSIVE

SPAN_EXCLUSIVE_EXCLUSIVE

Java代码:

String myString ="01234";
int start = 1;
int end = 3;
int spanFlag = Spannable.SPAN_INCLUSIVE_INCLUSIVE; // this is what is changing

// set the span
SpannableStringBuilder spannableString = new SpannableStringBuilder(myString);
ForegroundColorSpan foregroundSpan = new ForegroundColorSpan(Color.RED);
spannableString.setSpan(foregroundSpan, start, end, spanFlag);

// insert the text after the span has already been set
// (inserting at start index second so that end index doesn't get messed up)
spannableString.insert(end,"x");
spannableString.insert(start,"x");

textView.setText(spannableString);