SpannableString与SpannableStringBuilder

1,787 阅读3分钟

SpannableString和SpannableStringBuilder是用于设置文本样式的类。

SpannableString与SpannableStringBuilder可以实现的效果

类名实现效果
BackgroundColorSpan背景色
ClickableSpan文本可点击,有点击事件
ForegroundColorSpan前景色,文本颜色
MaskFilterSpan修饰效果,如模糊(BlurMaskFilter)、浮雕(EmbossMaskFilter)
RasterizerSpan光栅效果
StrikethroughSpan删除线(中划线
SuggestionSpan相当于占位符
UnderlineSpan下划线
AbsoluteSizeSpan绝对大小(文本字体
DynamicDrawableSpan设置图片,基于文本基线或底部对齐
ImageSpan图片
RelativeSizeSpan相对大小(文本字体
ScaleXSpan基于x轴缩放
StyleSpan字体样式:粗体、斜体等
SubscriptSpan下标(数学公式会用到
SuperscriptSpan上标(数学公式会用到
TextAppearanceSpan文本外貌(包括字体、大小、样式和颜色)
TypefaceSpan文本字体
URLSpan文本超链接

SpannableString与SpannableStringBuilder的使用场景

类名可变文本可变标记数据结构
SpannableString线性数组
SpannableStringBuilder区间树
下面介绍如何决定使用哪个类:
  • 需要将少量Span附加到单个文本对象,并且文本本身为只读,使用SpannableString
  • 需要在创建后修改文本,并且需要将Span附加到文本上,使用SpannableStringBuilder
  • 需要大量Span对象,无论文本本身是否为只读,都是有SpannableStringBuilder

样式实现,setSpan

文本样式通过setSpan(Object what, int start, int end, int flags)来实现设置,what参数指的是要引用于文本的Span,start和end参数指的是要将该Span应用到的文本索引,flags参数用于确定Span是否包含start和end索引对象的文本。flags的值的含义,如下表格所示:

flags的值实现效果
Spannable.SPAN_INCLUSIVE_EXCLUSIVE在文本前面插入新文本,使用该样式,后面插入新文本不使用该样式
Spannable.SPAN_INCLUSIVE_INCLUSIVE在文本前后插入新文本,都使用该样式
Spannable.SPSPAN_EXCLUSIVE_EXCLUSIVE在文本前后插入新文本,都不使用该样式
Spannable.SPAN_EXCLUSIVE_INCLUSIVE在文本前面插入新文本,不使用该样式,后面插入新文本使用该样式
注:当不插入文本时,对于修改文字颜色这种效果4种flags实现的效果是一样的,都是前闭后开,包含前面索引,不包含后面的索引;
4种flags值效果展示:
代码如下:
String text = "光阴似箭,日月如梭";
tv_content.setText(text);

String text1 = "光阴似箭,日月如梭";
SpannableStringBuilder spannableString1 = new SpannableStringBuilder(text1);
spannableString1.setSpan(new ForegroundColorSpan(Color.RED),2,7, Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
spannableString1.insert(2,"aa");
spannableString1.insert(5,"aa");
spannableString1.insert(11,"aa");
tv_content1.setText(spannableString1);

String text2 = "光阴似箭,日月如梭";
SpannableStringBuilder spannableString2 = new SpannableStringBuilder(text2);
spannableString2.setSpan(new ForegroundColorSpan(Color.BLUE),2,7, Spannable.SPAN_INCLUSIVE_INCLUSIVE);
spannableString2.insert(2,"bb");
spannableString2.insert(5,"bb");
spannableString2.insert(11,"bb");
tv_content2.setText(spannableString2);

String text3 = "光阴似箭,日月如梭";
SpannableStringBuilder spannableString3 = new SpannableStringBuilder(text3);
spannableString3.setSpan(new ForegroundColorSpan(Color.GREEN),2,7, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
spannableString3.insert(2,"cc");
spannableString3.insert(5,"cc");
spannableString3.insert(11,"cc");
tv_content3.setText(spannableString3);

String text4 = "光阴似箭,日月如梭";
SpannableStringBuilder spannableString4 = new SpannableStringBuilder(text4);
spannableString4.setSpan(new ForegroundColorSpan(Color.YELLOW),2,7, Spannable.SPAN_EXCLUSIVE_INCLUSIVE);
spannableString4.insert(2,"dd");
spannableString4.insert(5,"dd");
spannableString4.insert(11,"dd");
tv_content4.setText(spannableString4);

SpannableStringBuilder使用示例

效果如下:

代码如下:

String text5 = "我的电话:123456789;我的邮箱:abc123@163.com;头像:";
SpannableStringBuilder spannableString5 = new SpannableStringBuilder(text5);
//设置背景色
spannableString5.setSpan(new BackgroundColorSpan(Color.GRAY), 0, 4, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
//设置文本超链接
spannableString5.setSpan(new URLSpan("tel:123456789"), 5, 14, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
//设置下划线
spannableString5.setSpan(new UnderlineSpan(), 15, 19, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
//设置删除线
spannableString5.setSpan(new StrikethroughSpan(), 21, 34, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
//获取Drawable资源
Drawable d = getResources().getDrawable(R.mipmap.ic_launcher);
d.setBounds(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());
//创建ImageSpan,然后用ImageSpan来替换文本
ImageSpan imgspan = new ImageSpan(d, ImageSpan.ALIGN_BASELINE);
//设置图片
spannableString5.setSpan(imgspan, text5.length() - 1, text5.length(), Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
tv_content5.setText(spannableString5);
tv_content5.setMovementMethod(LinkMovementMethod.getInstance());

需求实现

需求描述:把输入框中输入内容,提交到服务器,服务器检测提交内容是否合法,把不合法的语句以集合形式返回,客户端把不合法的语句标红显示。省去提交网络过程,敏感词为固定内容; 实现效果,如图所示: 代码实现:
默认敏感词:

//敏感词   
private List<String> words;

words = new ArrayList<>();
words.add("太多");
words.add("精彩");
words.add("abc");

查找敏感词,并标为红色

private void setKeyWordColor() {
    if (words == null || words.size() < 0) {
        return;
    }
    Editable text = et_input.getText();
    if (text == null || TextUtils.isEmpty(text.toString())) {
        return;
    }
    String content = et_input.getText().toString();
    //避免死循环
    if (content.equals(lastContent)) {
        return;
    }
    lastContent = content;

    SpannableStringBuilder builder = new SpannableStringBuilder(content);
    //把文字置为初始色
    builder.setSpan(new ForegroundColorSpan(Color.parseColor("#333333")), 0, content.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
    for (int i = 0; i < words.size(); i++) {
        String str = words.get(i);
        if (TextUtils.isEmpty(str)) {
            continue;
        }
    
        if (!content.toLowerCase().contains(str.toLowerCase())) {
            continue;
        }
    
        //找到敏感词位置
        int index = content.toLowerCase().indexOf(str.toLowerCase());
        while (index >= 0) {
            builder.setSpan(new ForegroundColorSpan(Color.parseColor("#EA5B4B")), index, index + str.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
            index = content.toLowerCase().indexOf(str.toLowerCase(), index + str.length());
        }
    }
    
    int selectionStart = et_input.getSelectionStart();
    et_input.setText(builder);
    //设置光标为之前的位置
    et_input.setSelection(selectionStart);
}

输入文本检测,当输入相同敏感词时,直接标为红色:

et_input.addTextChangedListener(new TextWatcher() {
    @Override
    public void beforeTextChanged(CharSequence s, int start, int count, int after) {

    }

    @Override
    public void onTextChanged(CharSequence s, int start, int before, int count) {

    }

    @Override
    public void afterTextChanged(Editable s) {
        //输入相同的敏感词,不再请求接口,立即标位红色
        setKeyWordColor();
    }
});

参考资料

官方文档:developer.android.google.cn/guide/topic…
菜鸟教程:www.runoob.com/w3cnote/and…