Android设置TextView样式SpannableString教程
作者:流浪汉kylin
前言
最近没什么比较好的思路,所以基本是想到什么写什么。很多人有碰到过这样的需求,展示一段文字,但是这段文字中间的几个字颜色不同,比如这样:
展示一段 这几个字颜色不同\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是不设效果,最后一个是混合效果,来看看执行的结果
最后说一下,kotlin的话,不需要这样的方式,它能有更简洁的封装方式去实现这样的效果,我这里就不过多介绍了。
更多关于Android设置TextView样式的资料请关注脚本之家其它相关文章!