如下图所示,在App开发过程中很多时候需要对文字做类似如下处理:
给文字加颜色,加背景,加下划线,可点击,加中划线,字体大小调整,甚至还有中间插入一个小icon之类的。
好在Android SDK对这此功能是支持的,通过SpannableStringBuilder
和ParcelableSpan
的众多扩展子类完成,下面是Android默认实现方式:
String str = "红字, 绿背景,下划线|灰色字,可点击,中划线,大号字";
SpannableStringBuilder builder = new SpannableStringBuilder(str);
//设置颜色
builder.setSpan(new ForegroundColorSpan(Color.RED), 0, 2,
// setSpan时需要指定的 flag,Spanned.SPAN_EXCLUSIVE_EXCLUSIVE(前后都不包括).
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
//超链接
builder.setSpan(new BackgroundColorSpan(Color.GREEN), 2, 8, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
// 下划线|灰色字
builder.setSpan(new UnderlineSpan(), 8, 16, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
builder.setSpan(new BackgroundColorSpan(Color.GRAY), 8, 16, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
//删除线
builder.setSpan(new ClickableSpan() {
@Override
public void onClick(View widget) {
Toast.makeText(MainActivity.this, "hello", Toast.LENGTH_SHORT).show();
}
}, 16, 20, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
tv.setMovementMethod(LinkMovementMethod.getInstance());
// 中划线
builder.setSpan(new StrikethroughSpan(), 20, 24, Spannable.SPAN_INCLUSIVE_INCLUSIVE);
// 大号字
builder.setSpan(new AbsoluteSizeSpan(80), 24, 27, Spannable.SPAN_INCLUSIVE_INCLUSIVE);
//将文字赋予TextView
tv.setText(builder);
我想第一次使用此API可能会有些犯晕,毕竟其中太多需要计算的span起始和结束的index,虽然算不上非常复杂,但是也挺烦人。其实,完全可以借鉴JDK中对StringBuilder API的设计进行对其改造。
首先,先看看改造后的使用,看如何实现上图里的效果:
TextView textView = (TextView) findViewById(R.id.textView);
textView.setMovementMethod(LinkMovementMethod.getInstance());
StyleStringBuilder builder = new StyleStringBuilder();
builder.append(new StyleString("红字, ").setTextColor(ContextCompat.getColor(this, R.color.red)));
builder.append(new StyleString("绿背景, ").setBackgroundColor(ContextCompat.getColor(this, R.color.green)));
builder.append(new StyleString("下划线|灰色字, ").setUnderline().setTextColor(ContextCompat.getColor(this, R.color.grey)));
builder.append(new StyleString("可点击").setClickable(new ClickableSpan() {
@Override
public void onClick(View widget) {
Toast.makeText(MainActivity.this, "clicked", Toast.LENGTH_SHORT).show();
}
}));
builder.append(new StyleString("中划线").setStrikeThrough());
builder.append(new StyleString("大号字").setTextSize(getResources().getDimensionPixelOffset(R.dimen.text_size_large)));
textView.setText(builder.build());
我相信大家对这种风格的api是相对很熟悉的,可读性也尚可,来看看如何屏蔽那些琐碎的index的:
- StyleString:
public class StyleString {
private final String mText;
private final SpannableStringBuilder mBuilder;
public StyleString(String text) {
if (text == null) {
text = "";
}
mBuilder = new SpannableStringBuilder();
mBuilder.append(mText = text);
}
public StyleString setTextColor(@ColorInt int color) {
mBuilder.setSpan(new ForegroundColorSpan(color),
0, mText.length(),
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
return this;
}
/**
* @deprecated please use {@link #setTextColor(int)} instead
*/
public StyleString setForegroundColor(int color) {
return setTextColor(color);
}
public StyleString setBackgroundColor(@ColorInt int color) {
mBuilder.setSpan(new BackgroundColorSpan(color), 0, mText.length(),
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
return this;
}
/**
* @deprecated please use {@link #setTextSize(int)} instead
*/
public StyleString setFontSizePX(int sizeInPx) {
return setTextSize(sizeInPx);
}
public StyleString setTextSize(int sizeInPx) {
mBuilder.setSpan(new AbsoluteSizeSpan(sizeInPx), 0, mText.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
return this;
}
/**
* About typeFace see {@link Typeface}
*/
public StyleString setFontStyle(int typeFace) {
mBuilder.setSpan(new StyleSpan(typeFace), 0, mText.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
return this;
}
public StyleString setUnderline() {
mBuilder.setSpan(new UnderlineSpan(), 0, mText.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
return this;
}
/**
* If StyleString was used as content of TextView or EditText,
* setMovementMethod should be called like below:
*
*
* TextView textView = new TextView(context);
* textView.setMovementMethod(LinkMovementMethod.getInstance());
*
*/
public StyleString setClickable(ClickableSpan clickable) {
mBuilder.setSpan(clickable, 0, mText.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
return this;
}
public StyleString setUri(String uri) {
mBuilder.setSpan(new URLSpan(uri), 0, mText.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
return this;
}
public StyleString setStrikeThrough() {
mBuilder.setSpan(new StrikethroughSpan(), 0, mText.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
return this;
}
public SpannableStringBuilder toStyleString() {
return mBuilder;
}
}
- StyleStringBuilder:
public class StyleStringBuilder {
private final SpannableStringBuilder builder = new SpannableStringBuilder();
public StyleStringBuilder() {
}
public StyleStringBuilder(CharSequence text) {
builder.append(text);
}
public StyleStringBuilder(StyleString styleString) {
builder.append(styleString.toStyleString());
}
public StyleStringBuilder append(CharSequence text) {
builder.append(text);
return this;
}
public StyleStringBuilder append(StyleString styleString) {
builder.append(styleString.toStyleString());
return this;
}
public Spanned build() {
return builder;
}
public static CharSequence formatString(Context context, String text, String regular, int colorResId) {
try {
SpannableStringBuilder builder = new SpannableStringBuilder(text);
Pattern p = Pattern.compile(regular);
Matcher matcher = p.matcher(text);
int start = 0;
int end = 0;
while (matcher.find()) {
if (matcher.start() == end) {
end = matcher.end();
} else {
if (start != end) {
ForegroundColorSpan span = new ForegroundColorSpan(ContextCompat.getColor(context, colorResId));
builder.setSpan(span, start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
start = matcher.start();
end = matcher.end();
}
}
if (start != end) {
ForegroundColorSpan span = new ForegroundColorSpan(ContextCompat.getColor(context, colorResId));
builder.setSpan(span, start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
return builder;
} catch (Exception e) {
return text;
}
}
}
至于为什么没有对图片等其他特性支持是因为一时半会没有加那些span的支持,自己参考着扩展就好,因为这里已经没有什么秘密了。