敞开成长之旅!这是我参加「日新计划 2 月更文挑战」的第 7 天,点击查看活动详情

前语

最近没什么比较好的思路,所以基本是想到什么写什么。很多人有碰到过这样的需求,展现一段文字,但是这段文字中间的几个字色彩不同,比方这样:
展现一段 这几个字色彩不同\color{red}{这几个字色彩不同} 的文字
只是其中有一段要换文字的款式而已,其它的显示正常。有的人完成这个功用就用3个TextView去完成的,其实这是不对的,有一些开发经验的就知道有个类是SpannableString,它能完成这个功用。

SpannableString的运用

为什么它能完成这个功用呢?其实你setTextView终究仍是制作出来,比方咱们都知道Canvas是能制作文字的,假如我用Canvas去制作文字的话,我就很简单能完成各种作用,包含上面的一段文字变色。其实SpannableString的作用就相当于Canvas去制作的作用相同。

而spannableStringBuilder相对于SpannableString而言是多了拼接的作用,他们都相同,所以我这儿就以spannableStringBuilder来说,假如我要运用spannableStringBuilder去完成上面的作用,能够这样写

SpannableStringBuilder spannableStringBuilder = new SpannableStringBuilder();
spannableStringBuilder.append(“展现一段这几个字色彩不同的文字”);
ForegroundColorSpan foregroundColorSpan = new ForegroundColorSpan(Color.RED);
spannableStringBuilder.setSpan(foregroundColorSpan, 4, 11, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
textview.setText(spannableStringBuilder);

这样就能完成上面的作用了,咱们能够简单看看ForegroundColorSpan,它有个updateDrawState方法

/**
 * Updates the color of the TextPaint to the foreground color.
 */
@Override
public void updateDrawState(@NonNull TextPaint textPaint) {
    textPaint.setColor(mColor);
}

TextPaint承继Paint,能看出这是一个设置画笔的动作。而spannableStringBuilder的setSpan方法的第一个参数就是指Span,那让咱们来看看系统提供了哪些常见的Span(我这儿就列举几个常用的)

  • ForegroundColorSpan:文本色彩
  • BackgroundColorSpan:布景色彩
  • AbsoluteSizeSpan:字体的大小(绝对值)
  • RelativeSizeSpan:字体的大小(相对值,xx倍)
  • StyleSpan:字体的风格
  • UnderlineSpan:下划线
  • StrikethroughSpan:删除线

除了这些之外还有RasterizerSpan、ImageSpan、TypefaceSpan等等,感兴趣的能够去看官方文档,我这就不介绍这么多了。

然后咱们看setSpan的最终一个参数flags,一共有4种类型:

  • SPAN_EXCLUSIVE_EXCLUSIVE:包含前面,包含后边
  • SPAN_EXCLUSIVE_INCLUSIVE:包含前面,不包含后边
  • SPAN_INCLUSIVE_EXCLUSIVE:不包含前面,包含后边
  • SPAN_INCLUSIVE_INCLUSIVE:不包含前面,不包含后边

这是什么意思呢?看得出它的命名有两个单词_EXCLUSIVE_EXCLUSIVE这样的,第一个表明影响范围是前面,第二个表明影响范围是后边。EXCLUSIVE表明包含,INCLUSIVE表明不包含。

假设我这句话“123456”,我设置2种款式,123用ForegroundColorSpan设置字体为红色,456用BackgroundColorSpan设置布景为红色。假设123运用SPAN_EXCLUSIVE_EXCLUSIVE,456运用SPAN_INCLUSIVE_INCLUSIVE,那终究作用是123字体是红色,456字体和布景都是红色。

封装SpannableString

简单的介绍了SpannableString的作用和调用方法,咱们来看看详细怎样运用,假如你按照上面的代码,每设置一个文字作用,都要写这么一串代码,那终究看起来会很臃肿

SpannableStringBuilder spannableStringBuilder = new SpannableStringBuilder();
spannableStringBuilder.append(“展现一段这几个字色彩不同的文字”);
ForegroundColorSpan foregroundColorSpan = new ForegroundColorSpan(Color.RED);
spannableStringBuilder.setSpan(foregroundColorSpan, 4, 11, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
textview.setText(spannableStringBuilder);

所以咱们能够封装一下(把自己会用上的作用先整合起来)

public class SpanStringUtils {
    private Context context;
    private SpannableStringBuilder spannableStringBuilder;
    private int lp = 0;
    private int rp = 0;
    private List<Span> list = new ArrayList<>();
    public SpanStringUtils(Context context) {
        this.context = context;
        spannableStringBuilder = new SpannableStringBuilder();
    }
    public SpanStringUtils append(Span span) {
        list.add(span);
        return this;
    }
    public CharSequence create() {
        if (list == null || list.isEmpty()) {
            return null;
        }
        for (int i = 0; i < list.size(); i++) {
            Span span = list.get(i);
            createSpan(span);
        }
        return spannableStringBuilder;
    }
    private void createSpan(Span span) {
        if (span != null) {
            String str = span.textRId == -1 ? span.text : context.getString(span.textRId);
            rp = lp + str.length();
            spannableStringBuilder.append(str);
            if (span.spannable != null) {
                spannableStringBuilder.setSpan(span.spannable, lp, rp, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
            }
            if (span.textColor != -1) {
                ForegroundColorSpan foregroundColorSpan = new ForegroundColorSpan(context.getResources().getColor(span.textColor));
                spannableStringBuilder.setSpan(foregroundColorSpan, lp, rp, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
            }
            if (span.backgroundColor != -1) {
                BackgroundColorSpan backgroundColorSpan = new BackgroundColorSpan(context.getResources().getColor(span.backgroundColor));
                spannableStringBuilder.setSpan(backgroundColorSpan, lp, rp, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
            }
            if (span.relativeSize > 0) {
                RelativeSizeSpan relativeSizeSpan = new RelativeSizeSpan(span.relativeSize);
                spannableStringBuilder.setSpan(relativeSizeSpan, lp, rp, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
            }
            if (span.xSpan > 0) {
                ScaleXSpan scaleXSpan = new ScaleXSpan(span.xSpan);
                spannableStringBuilder.setSpan(scaleXSpan, lp, rp, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
            }
            if (span.style != -1) {
                StyleSpan styleSpan = new StyleSpan(span.style);
                spannableStringBuilder.setSpan(styleSpan, lp, rp, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
            }
            if (span.isUnderline) {
                UnderlineSpan underlineSpan = new UnderlineSpan();
                spannableStringBuilder.setSpan(underlineSpan, lp, rp, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
            }
            if (span.isStrikethrough) {
                StrikethroughSpan strikethroughSpan = new StrikethroughSpan();
                spannableStringBuilder.setSpan(strikethroughSpan, lp, rp, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
            }
            lp = rp;
        }
    }
    public static class Span {
        private String text;
        private int textRId = -1;
        private int textColor = -1;
        private int backgroundColor = -1;
        private float relativeSize;
        private float xSpan;
        private int style = -1;
        private boolean isUnderline;
        private boolean isStrikethrough;
        private ParcelableSpan spannable;
        public Span setText(String text) {
            this.text = text;
            return this;
        }
        public Span setText(int rId) {
            this.textRId = rId;
            return this;
        }
        public Span setTextColor(int textColor) {
            this.textColor = textColor;
            return this;
        }
        public Span setBackgroundColor(int backgroundColor) {
            this.backgroundColor = backgroundColor;
            return this;
        }
        public Span setRelativeSize(float relativeSize) {
            this.relativeSize = relativeSize;
            return this;
        }
        public Span setxSpan(float xSpan) {
            this.xSpan = xSpan;
            return this;
        }
        public Span setStyle(int style) {
            this.style = style;
            return this;
        }
        public Span setUnderline(boolean underline) {
            isUnderline = underline;
            return this;
        }
        public Span setStrikethrough(boolean strikethrough) {
            isStrikethrough = strikethrough;
            return this;
        }
        public Span setSpannable(ParcelableSpan spannable) {
            this.spannable = spannable;
            return this;
        }
    }
}

因为是用java写的,所以设置成一个链式调用的方法终究看起来会比较美观。Span表明每一段文字的作用,lp和rp是指向每段文字的范围,其它的地方应该都能很简单看懂。

然后在运用的时候这样调用

TextView testTv = findViewById(R.id.tv_test);
testTv.setTextColor(Color.parseColor("#ffffff"));
ForegroundColorSpan foregroundColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.orange));
CharSequence charSequence = new SpanStringUtils(this)
        .append(new SpanStringUtils.Span().setText("abcde").setTextColor(R.color.blue))
        .append(new SpanStringUtils.Span().setText("qqqq").setBackgroundColor(R.color.blue))
        .append(new SpanStringUtils.Span().setText("ccc").setRelativeSize(1.5f))
        .append(new SpanStringUtils.Span().setText("dddddd"))
        .append(new SpanStringUtils.Span().setText("eee").setSpannable(foregroundColorSpan))
        .append(new SpanStringUtils.Span().setText("8s8s8s8s").setStyle(Typeface.BOLD))
        .append(new SpanStringUtils.Span().setText("kk").setUnderline(true))
        .append(new SpanStringUtils.Span().setText("M").setStrikethrough(true))
        .append(new SpanStringUtils.Span()
                .setText("LAST")
                .setTextColor(R.color.jh_orange)
                .setRelativeSize(3f)
                .setUnderline(true)
                .setStrikethrough(true))
        .create();
testTv.setText(charSequence);

前面的都是单个作用,dddddd是不设作用,最终一个是混合作用,来看看执行的结果

Android教你设置TextView样式(SpannableString)

最终说一下,kotlin的话,不需要这样的方法,它能有更简洁的封装方法去完成这样的作用,我这儿就不过多介绍了。